linux下TCP socket编程入门案例(一)——阻塞的TCP server&client

在项目上做的通信方面的内容,但是呢,又没这方面的经验,从头学起,分享给需要这方面内容的朋友们。

TCP和UDP通信

使用socket编程有两种通信协议可以选择,一种是数据报通信,另一种是流通信。​​

(1)数据报通信

数据报通信协议,就是我们常说的UDP(User Data Protocol 用户数据报协议)。UDP是一种无连接的协议,这意味着我们每次发送数据报时,需要同时发送本机的socket描述符和接收端的socket描述符。因此,在每次通信时都需要发送额外的数据。

(2)流通信

流通信协议,也叫做TCP(Transfer Control Protocol,传输控制协议)。和UDP不同,TCP是一种基于连接的协议。在使用流通信之前,我们必须在通信的一对socket之间建立连接。其中一个socket作为服务器监听连接请求,另一个则作为客户端进行连接请求。一旦两个socket建立好了连接,它们就可以单向或双向进行数据传输。

进行socket编程使用UDP还是TCP呢,选择基于何种协议的socket编程取决于具体的客户端-服务器端程序的应用场景。在UDP中,每次发送数据报时,需要附带上本机的socket描述符和接收端的socket描述符。而TCP是基于连接的协议,在通信的socket对之间需要在通信之前先建立连接,因此TCP相比UDP比较耗时。在UDP中,数据报数据在大小上有64KB的限制;而TCP中也不存在这样的限制。一旦使用TCP通信的socket对建立了连接,他们之间的通信就类似IO流,所有的数据会按照发送时的顺序读取。UDP是一种不可靠的协议,发送的数据报不一定会按照其发送顺序被接收端的socket接受。而TCP是一种可靠的协议,接收端收到的包的顺序和包在发送端的顺序是一致的。

简而言之,TCP适合于如远程登录(rlogin,telnet)和文件传输(FTP)这类的网络服务。因为这些需要传输的数据大小不确定。而UDP相比TCP更加简单一些,常用来实现实时性较高或者丢包不重要的一些服务

Socket简介

Socket,又称套接字,是计算机网络通信的基本技术之一。如今大部分基于网络的软件,如浏览器、即时通讯工具、P2P下载等都是基于Socket实现的。

在使用Socket编程时,需要重点了解几个API,socket()、bind()、listen()、connect()、accept()、send()和recv()。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept()阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把处理结果发送给客户端,客户端读取数据,最后关闭连接,一次完整的交互结束。

(1)socket()

int socket(int protofamily, int type, int protocol);//返回sockfd
       返回值:如果函数调用成功,会返回一个标识这个套接字的文件描述符(大于零的数);失败返回-1。
       函数的三个参数分别为:  
       protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、 AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定 了要用一个绝对路径名作为地址。 
      type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、 SOCK_PACKET、SOCK_SEQPACKET等。  
      protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、 IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC 传输协议。
(2)bind()

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
       返回值:正常返回0,错误返回-1
       参数:
       sockfd:即socket描述字,通过socket()函数创建,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
       addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
       addrlen:对应的是地址的长度。

(3)listen()
       int listen(int sockfd, int backlog);
       返回值:正常返回0,错误返回-1
       参数:
       sockfd:要监听的socket描述字,
       backlog:为相应socket可以排队的最大连接个数。
       socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户端的连接请求。

(4)connect()
       int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
       返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中.
       参数:
       sockfd:客户端的socket描述字
       addr:服务器的socket地址
       addrlen:socket地址的长度

(5)accept()
      int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd
      返回值:成功时返回一个新的SOCKFD值(非负值),失败时返回-1
      参数:
      sockfd是监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个端口号,而此时这个端口号与这个套接字关联。

     addr是一个结果参数,它用来接受一个返回值,返回值指定客户端的地址,可以置为NULL。
     addrlen它也是结果参数,用来接受上述addr的结构的大小,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。

(6)send()
不论是客户端还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
int send( SOCKET s, const char FAR *buf, int len,int flags);   
返回值:成功返回所发送数据的总数;对端连接关闭返回0;失败返回-1
参数:
第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。 

(7)recv()
不论是客户端还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。 
int recv( SOCKET s, char FAR *buf,  int len,int flags);
返回值:成功返回其实际copy的字节数;对端连接关闭返回0;失败返回-1
参数:
第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。

(8)close()
int close(int fd);
头文件:#include
返回值:成功返回0,出错返回-1
参数:要关闭的套接字描述符

注意,实际开发中,不要去send 0字节的数据,这是一种很糟糕的做法这会使函数的返回值0产生歧义。recv和send不一定是一一对应的(一般情况下是一一对应),也就是说并不是send一次,就一定recv一次就接收完,有可能send一次,recv多次才接收完;也可能send多次,一次recv就接收完了。

linux下TCP socket编程入门案例(一)——阻塞的TCP server&client_第1张图片 TCP Socket编程步骤

 附按照上面的顺序编写的小demo:

server.cpp

/**

TCP server(阻塞式)

**/
#include 
#include
#include
#include
#include
#include
#include
#include

int main(int argc, char** argv){
	
    int server_socket;//服务端套接字
    int client_socket;//客户端套接字
    struct sockaddr_in  servaddr;//服务器网络地址
    struct sockaddr_in  remote_addr;///客户端地址
    char  buff[BUFSIZ];//数据传送缓冲区
    int  n;
    socklen_t addr_size;//结构体长度,accept函数中该参数类型为socklen_t *

    memset(&servaddr, 0, sizeof(servaddr));//结构体初始化清零
    servaddr.sin_family = AF_INET;//IPv4
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(8888);
	
    addr_size = sizeof(struct sockaddr_in);
	
	//创建服务端套接字
	//1. create socket
    if( (server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){
        printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
        return -1;
    }

	//绑定套接字到ip和端口
    //2.bind socket & port to server
    if( bind(server_socket, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
        return -1;
    }

	//监听连接请求,监听队列长度为10
    //3.listen
    if( listen(server_socket, 10) < 0){
        printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
        return -1;
    }

    printf("======waiting for client's request======\n");
	
    while(true){
		
		//等待客户端连接请求到达
		//4.accept
		if( (client_socket = accept(server_socket, (struct sockaddr *)&remote_addr, &addr_size)) < 0){
			printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
			continue;
		}
		printf("conncet with client: %s\n",inet_ntoa(remote_addr.sin_addr));//
		
		if(send(client_socket,"welcome\n",7,0) < 0){
			printf("send message error: %s(errno: %d)",strerror(errno),errno);
			continue;
		}

			//5.read message from socket
			while((n = recv(client_socket, buff, BUFSIZ, 0)) > 0){
				buff[n] = '\0';
				printf("receive message from client: %s\n", buff);
				
				if(send(client_socket,buff,strlen(buff),0) < 0){
					printf("send message error: %s(errno: %d)",strerror(errno),errno);
					close(client_socket);
					close(server_socket);
					return -1;
				}
			}
    }

	//关闭套接字
	//close(client_socket);
    //close(server_socket);
	
    return 0;
}

 client.cpp

/**

TCP client(阻塞式)

**/
#include
#include
#include
#include
#include
#include
#include 
#include

int main(int argc, char** argv){
    int   client_socket; //客户端套接字
    int n;
    char  buff[BUFSIZ];//数据传输缓冲区
    struct sockaddr_in  remote_addr;//服务器端网络地址

    memset(&remote_addr, 0, sizeof(remote_addr));
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_port = htons(8888);
    remote_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	//创建客户端套接字
	if( (client_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
        return -1;
    }

	//连接服务器
    if( connect(client_socket, (struct sockaddr*)&remote_addr, sizeof(remote_addr)) < 0){
        printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
        return -1;
    }
	
	printf("connected to server success\n");
	
	//接收服务器欢迎消息
	if( (n = recv(client_socket,buff,BUFSIZ,0)) > 0){
		buff[n] = '\0';
		printf("receive message from server: %s\n", buff);
	}
	
	/*循环发送接收消息并打印*/
	while(true){
		printf("send message to server:");
		
		scanf("%s",buff);
		//输入quit,则client与server断开连接
		if( !strcmp(buff,"quit")){
			printf("客户端断开连接...\n");
			close(client_socket);
			break;
		}
		
		//send to server
		if((n = send(client_socket,buff,strlen(buff),0)) < 0){
			printf("send message error: %s(errno:%d)\n",strerror(errno),errno);
		}
		
		//receive from server
		if((n = recv(client_socket,buff,BUFSIZ,0)) > 0){
			buff[n] = '\0';
			printf("recaeived:%s\n",buff);
		}
		
	}

	close(client_socket);
	
    return 0;
}


运行截图:

刚开始学习socket编程,不足之处请大家多多指点。

你可能感兴趣的:(C/C++,Linux,学习笔记,TCP,socket,网络编程)