C语言 socket学习整理

分三个topic来熟悉C语言的socket使用方法。

  1. 一台client与一台server之间的双向TCP通讯。
  2. 使用select接口实现的多台client与一台server之间的通讯。
  3. 使用epoll接口实现的多台client与一台server之间的通讯。

TCP通信模型与UDP通信模型的区别

  •  UDP通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,“写信"”
  •  TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,“打电话”"

这里仅以TCP通信为例。先放上TCP协议socket的编程流程图:

在代码调试过程中遇到的知识点整理如下:

  1. 客户端可以用bind来绑定IP与端口,但不是必须的。
  2. accept函数的第三个参数,在调用之前需要设置为第二个参数的长度,否则会出现得到的客户端IP地址/端口为0.0.0.0:0。

    struct sockaddr_in client_address = {0};  
    socklen_t address_len = sizeof(client_address); 
    int client_sock_fd = accept(listen_fd, (struct sockaddr *)&client_address, &address_len);
    

  3. socket函数的第三个参数protocolprotocol:指定要与socket一起使用的特定协议,一般可以置为0,表示使用默认的常用的也就是sock_stream,流式传输,默认是TCP协议;sock_dgram,数据报形式,默认是UDP协议。

  4. select的实现是通过对设备的轮询来实现的,每次调用FD_ISSET()函数后 ,会把原来待检测的但是仍没就绪的描述字清0了。所以,每次调用select()前要重新调用FD_SET()来设置一下待检测的描述设备。

一台client与一台server之间的双向TCP通讯

client端代码如下:

#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for close
#include <arpa/inet.h>
#include <pthread.h>

#define SERVPORT 3333
#define MAXDATASIZE 100

static void dump_data(char* name,char* buf,int len)
{
	int i;
	printf("buf:");
	for(i=0; i<len; i++){
		if(i%8 == 0)
			printf("n");
		printf("%4d ",buf[i]);	
	}
	printf("n");	
}

static void *thread(void *param1)  
{   
    char buf[MAXDATASIZE];
    int buf_size = 0;
    int new_fd = *((int*)param1);
    while(1){
    	buf_size = recv(new_fd,buf,MAXDATASIZE+1,0);
    	printf("buf_size:%dn",buf_size);
    	if(buf_size <= 0 )
    		break;
    	printf("client received:%sn",buf);	
    	
    	//dump_data("buf",buf,buf_size);
    	usleep (10000);
    }
    return NULL;  
}
	
int main(int argc,char *argv[])
{
	int sockfd,sendbytes;
	char buf[MAXDATASIZE+1];
	char **pptr;
	//char buf_num[6] = {0,};
	//char str[6] = {0,};
	//int num = 0;
	struct hostent *host;
	struct sockaddr_in serv_addr;
	struct sockaddr_in clientAddr;//客户端地址
  char ipAddress[INET_ADDRSTRLEN];//保存点分十进制的ip地址
	socklen_t clientAddrLen = sizeof(clientAddr);
		
	if(argc < 2){
		fprintf(stderr,"Please enter the server's hostname!n");
		exit(1);
	}
	
	/*地址解析函数*/
	if((host=gethostbyname(argv[1]))==NULL){
		perror("gethostbyname");
		exit(1);
	}
	
	//strcpy(str,argv[2]);
	/*创建socket*/
	if((sockfd=socket(AF_INET,SOCK_STREAM,0))== -1){
		perror("socket");
		exit(1);
	}
	
	printf("client sockfd:%dn",sockfd);
	/*设置sockaddr_in 结构体中相关参数*/
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_port=htons(SERVPORT);
	serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
	bzero(&(serv_addr.sin_zero),8);
	
	/*调用connect函数主动发起对服务器端的连接*/
	if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))== -1){
		perror("connect");
		exit(1);
	}
	
	getsockname(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);//获取sockfd表示的连接上的本地地址
  printf("client address = %s:%dn", inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress)), 
  	ntohs(clientAddr.sin_port));

	printf("official hostname:%sn", host->h_name);   //主机规范名
	for(pptr = host->h_aliases; *pptr != NULL; pptr++)   //将主机别名打印出来
		printf("alias: %sn", *pptr);	

  //新建一个线程来进行发送操作.
  pthread_t ntid;
  pthread_create(&ntid,NULL,thread,&sockfd);
    
	/*发送消息给服务器端*/
	while(1){
		//scanf("%s",buf);
		ssize_t size = read(STDIN_FILENO, buf, sizeof(buf));
    if(!strcmp(buf,"quit"))
    	break;

		printf("client send:%s size:%dn",buf,size);
		if(size > 0){
			if((sendbytes=send(sockfd,buf,size,0))== -1){
				perror("send");
				break;
			}
		}
		usleep (10000);
		//sleep(2);
	}
	close(sockfd);
	
	return 0;
}

server端代码如下:

#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>

#define SERVPORT 3333
#define BACKLOG 10
#define MAX_CONNECTED_NO 10
#define MAXDATASIZE 100

char recv_buf[MAXDATASIZE+1];

static void dump_data(char* name,char* buf,int len)
{
	int i;
	printf("buf:");
	for(i=0; i<len; i++){
		if(i%8 == 0)
			printf("n");
		printf("%4d ",buf[i]);	
	}
	printf("n");	
}

void* server_send(void* param)
{
   int new_fd = *(int*)param;
   while(1){
     bzero(recv_buf, MAXDATASIZE + 1);
     ssize_t size = read(STDIN_FILENO, recv_buf, sizeof(recv_buf));
     printf("recv_buf size:%d size:%dn",sizeof(recv_buf),size);
     printf("server send %sn",recv_buf);
     if(size > 0){
	     int len = send(new_fd, recv_buf,size, 0);
	     if(len <= 0)
	       break;
	     else{
	     		;	//dump_data("recv_buf",recv_buf,sizeof(recv_buf));
	     }
     }	 
   }
   exit(0);
}
	
int main()
{
	struct sockaddr_in server_sockaddr,client_sockaddr,peerAddr;
	struct sockaddr listendAddr,connectedAddr;
	//int recvbytes;
	socklen_t sin_size;
	//socklen_t peerLen;
	int sockfd,client_fd;
	//char ipAddr[INET_ADDRSTRLEN];//保存点分十进制的地址
	//fd_set readfd;
	//fd_set writefd;

	/*建立socket连接*/
	if((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1){
		perror("socket");
		exit(1);
	}
	printf("server sockfd=%dn",sockfd);
	
	/*设置sockaddr_in 结构体中相关参数*/
	server_sockaddr.sin_family=AF_INET;
	server_sockaddr.sin_port=htons(SERVPORT);
	server_sockaddr.sin_addr.s_addr=INADDR_ANY;
	bzero(&(server_sockaddr.sin_zero),8);
	/*绑定函数bind*/
	if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))== -1){
		perror("bind");
		exit(1);
	}
	printf("bind success!n");	
	
		/*调用listen函数*/
	if(listen(sockfd,BACKLOG)== -1){
		perror("listen");
		exit(1);
	}
	printf("listening....n");
	
	socklen_t listendAddrLen = sizeof(listendAddr);
	getsockname(sockfd, (struct sockaddr *)&listendAddr, &listendAddrLen);//获取监听的地址和端口
	
	struct sockaddr_in *tmp_sockaddr;
	tmp_sockaddr = (struct sockaddr_in *)&listendAddr;
  printf("listen address = %s:%dn", inet_ntoa(tmp_sockaddr->sin_addr), ntohs(tmp_sockaddr->sin_port));
  
	if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))== -1){
		perror("accept");
		exit(1);
	} 
	printf("accept client_fd:%dn",client_fd);
  //创建线程为了给客户端发送数据
  pthread_t ntid;
  int ret = pthread_create(&ntid,NULL,server_send,(void *)&client_fd);
  if(ret == 0)
  	printf("okn"); 
  	
	/*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
	while((recv(client_fd,recv_buf,MAXDATASIZE+1,0))>0){
	   printf("server recvived:%sn",recv_buf);
	}
	
	close(client_fd);
	close(sockfd);
	
	return 0;
}

PS:多台client的测试方式,可以在PC上使用Python实现client来验证比较方便,我这边是用两台PC+一块linux开发板来测试的。

 

使用select接口实现的多台client与一台server之间的通讯

select接口的实现不会造成堵塞,但是select的fd_set容量有限,当fd_set过大时系统开销较大。

client端代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for close
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <fcntl.h>

#define SERVPORT 3333
#define BUFFER_SIZE 1024

#define DUMP_DATA
#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len);
#endif
static void show_socket_info(int listen_fd);
static void show_peer_info(int listen_fd);
static int prepare_connect(struct hostent *host);
static void fd_set_init(fd_set* client_fd_set,int* max_fd,int client_fd);
static void send_stdin_to_server(fd_set* client_fd_set,int client_sockfd);
static void get_server_msg(fd_set* client_fd_set,int client_sockfd);
static void check_event(fd_set* client_fd_set,int* max_fd,int client_fd);
	
char recv_msg[BUFFER_SIZE];  
char input_msg[BUFFER_SIZE]; 
    	
int main(int argc,char *argv[])
{
	int client_sockfd = -1;
	
	//char buf_num[6] = {0,};
	char str[6] = {0,};
	//int num = 0;
	struct hostent *host;
	
	if(argc < 2){
		fprintf(stderr,"Please enter the server's hostname!n");
		exit(1);
	}
	
	/*地址解析函数*/
	if((host=gethostbyname(argv[1]))==NULL){
		perror("gethostbyname");
		exit(1);
	}
	
	strcpy(str,argv[2]);	//第二个参数为client名称
	
	int fd = 0;
	fd = open("/dev/mem", O_RDWR | O_NDELAY); 

	client_sockfd = prepare_connect(host);
	if(client_sockfd < 0){
		perror("prepare_connect");
		exit(0);		
	}

	fd_set client_fd_set;  
	int max_fd = -1;

	while(1){
		fd_set_init(&client_fd_set,&max_fd,client_sockfd);
    check_event(&client_fd_set,&max_fd,client_sockfd);	
	}
	return 0;
}

#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len)
{
	int i;
	printf("buf:");
	for(i=0; i<len; i++){
		if(i%8 == 0)
			printf("n");
		printf("%4d ",buf[i]);	
	}
	printf("n");	
}
#endif

static int prepare_connect(struct hostent *host)
{
	struct sockaddr_in serv_addr;
	int client_sockfd = -1;	
	char **pptr;
	/*创建socket*/
	if((client_sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))== -1){
		perror("socket");
		exit(1);
	}
	
	/*设置sockaddr_in 结构体中相关参数*/
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_port=htons(SERVPORT);
	serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
	bzero(&(serv_addr.sin_zero),8);
	
	/*调用connect函数主动发起对服务器端的连接*/
	if(connect(client_sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))== -1){
		perror("connect");
		return -1;
	}
	
	show_socket_info(client_sockfd);
	//show_peer_info(client_sockfd);

	printf("official hostname:%sn", host->h_name);   //主机规范名
	for(pptr = host->h_aliases; *pptr != NULL; pptr++)   //将主机别名打印出来
		printf("alias: %sn", *pptr);	

	printf("client_sockfd:%d n",client_sockfd);
	
	return client_sockfd;
}

static void fd_set_init(fd_set* client_fd_set,int* max_fd,int client_fd)
{ 
  FD_ZERO(client_fd_set);  
  FD_SET(STDIN_FILENO, client_fd_set);  
  if(*max_fd < STDIN_FILENO){  
  	*max_fd  = STDIN_FILENO;  
  }  
	FD_SET(client_fd, client_fd_set);
  if(*max_fd  < client_fd){  
  	*max_fd  = client_fd;  
  } 
  printf("max_fd:%d client_fd:%dn",*max_fd,client_fd);
  //dump_data("fd",(char*)client_fd_set,128);	      	
}

static void send_stdin_to_server(fd_set* client_fd_set,int client_sockfd)
{
	//将键盘输入消息发送给socket		
	if(FD_ISSET(STDIN_FILENO, client_fd_set)){  
    bzero(input_msg, BUFFER_SIZE);
    ssize_t size = read(STDIN_FILENO, input_msg, sizeof(input_msg));
    printf("client send:%s n",input_msg);
    printf("client_sockfd:%dn",client_sockfd);
    if(send(client_sockfd, input_msg, size, 0) == -1){  
    	perror("send error!n");  
    }  
  }  	
}

static void get_server_msg(fd_set* client_fd_set,int client_sockfd)
{
	if(FD_ISSET(client_sockfd, client_fd_set)){
    bzero(recv_msg, BUFFER_SIZE);  
    int byte_num = recv(client_sockfd, recv_msg, BUFFER_SIZE, 0); 
    if(byte_num > 0){  
      if(byte_num > BUFFER_SIZE){  
          byte_num = BUFFER_SIZE;
          recv_msg[byte_num] = '';
      }   
      printf("client:%d receive msg:%sn",client_sockfd,recv_msg);  
    }  
    else if(byte_num < 0){  
    	printf("receive error!n");  
    }  
    else{  
      printf("server is quitted!n");  
      exit(0);  
    }  	
  }	
}

static void check_event(fd_set* client_fd_set,int* max_fd,int client_fd)
{
	struct timeval tv;
  tv.tv_sec = 20;  
  tv.tv_usec = 0;  

	int ret = select(*max_fd + 1, client_fd_set, NULL, NULL, &tv); 
  if(ret < 0){  
    perror("select errn");  
    return;  
  }  
  else if(ret == 0){  
    printf("select tmon");  
    return;  
  }  
  else{
		//将键盘输入消息发送给socket
		send_stdin_to_server(client_fd_set,client_fd);
    get_server_msg(client_fd_set,client_fd);  	    	
  }			
}

static void show_socket_info(int listen_fd)
{
	struct sockaddr_in *tmp_sockaddr;
	struct sockaddr connectedAddr;

	int ret = -1;
	
	socklen_t connectedAddrLen = sizeof(connectedAddr);
	//获取listen_fd表示的连接上的本地地址
	ret = getsockname(listen_fd, (struct sockaddr *)&connectedAddr, &connectedAddrLen);
  if(ret){
  	perror("getsockname");
  	return;
  }	
	tmp_sockaddr = (struct sockaddr_in *)&connectedAddr;
	printf("socket address = %s:%dn", inet_ntoa(tmp_sockaddr->sin_addr), ntohs(tmp_sockaddr->sin_port));		
}

static void show_peer_info(int listen_fd)
{	
	struct sockaddr_in peerAddr;	
	char ipAddr[INET_ADDRSTRLEN];//保存点分十进制的地址
	socklen_t peerLen;
	int ret = -1;
	//获取listen_fd表示的连接上的对端地址
  ret = getpeername(listen_fd, (struct sockaddr *)&peerAddr, &peerLen); 
  if(ret){
  	perror("getpeername");
  	return;
  }
 	
  printf("socket peer address = %s:%dn", inet_ntop(AF_INET,&(peerAddr.sin_addr), ipAddr, sizeof(ipAddr)), 
  		ntohs(peerAddr.sin_port));	
}

server端代码如下:

#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>

#define SERVPORT 3333
#define BACKLOG 10
#define MAX_CONNECTED_NO 10
#define BUFFER_SIZE 1024
#define QUIT_CMD ".quit" 

int client_fds[MAX_CONNECTED_NO] = {0,};
char input_msg[BUFFER_SIZE];  
char recv_msg[BUFFER_SIZE]; 

#define DUMP_DATA
#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len);
#endif

static int socket_bind(int port);
static void fd_set_init(fd_set* serv_fd_set,int* max_fd,int listen_fd);
static void show_socket_info(int listen_fd);
static void show_peer_info(int listen_fd);
static void update_fd_set(fd_set* serv_fd_set,int* max_fd);
static void broadcast_stdin(fd_set* serv_fd_set);
static void handle_new_connect(fd_set* serv_fd_set,int listen_fd);
static void forward_from_recv(fd_set* serv_fd_set);
static void check_event(fd_set* serv_fd_set,int* max_fd,int listen_fd);
	
int main()
{    	
	int listen_fd = 0;
	
  listen_fd = socket_bind(SERVPORT);
  
	/*调用listen函数*/
	if(listen(listen_fd,BACKLOG)== -1){
		perror("listen");
		exit(1);
	}
	printf("listening....n");
	  
  //fd_set  
	fd_set server_fd_set;  
	int max_fd = -1; 
         
  while(1){
  	fd_set_init(&server_fd_set,&max_fd,listen_fd);
  	update_fd_set(&server_fd_set,&max_fd);
  	check_event(&server_fd_set,&max_fd,listen_fd);
  }	
	return 0;
}

static int socket_bind(int port)
{
	struct sockaddr_in server_sockaddr;
	int listen_fd;

	/*建立socket连接*/
	if((listen_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))== -1){
		perror("socket");
		exit(1);
	}
	printf("socket success!,listen_fd=%dn",listen_fd);
	
	/*设置sockaddr_in 结构体中相关参数*/
	server_sockaddr.sin_family=AF_INET;
	server_sockaddr.sin_port=htons(port);
	server_sockaddr.sin_addr.s_addr=INADDR_ANY;
	bzero(&(server_sockaddr.sin_zero),8);
	/*绑定函数bind*/
	if(bind(listen_fd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))== -1){
		perror("bind");
		exit(1);
	}
	
	printf("bind success!n");
	
	show_socket_info(listen_fd);
  
  return listen_fd;
}

static void fd_set_init(fd_set* serv_fd_set,int* max_fd,int listen_fd)
{ 
  FD_ZERO(serv_fd_set);  
  FD_SET(STDIN_FILENO, serv_fd_set);  
  if(*max_fd < STDIN_FILENO){  
      *max_fd  = STDIN_FILENO;  
  }  
	FD_SET(listen_fd, serv_fd_set);
  if(*max_fd  < listen_fd){  
      *max_fd  = listen_fd;  
  } 
  //printf("max_fd=%dn",*max_fd);
  //dump_data("fd",(char*)serv_fd_set,128);	
}

static void update_fd_set(fd_set* serv_fd_set,int* max_fd)
{
	int i;	
	for(i = 0; i < MAX_CONNECTED_NO; i++){
		//printf("client_fds[%d]=%dn", i, client_fds[i]);
    if(client_fds[i] != 0){  
      FD_SET(client_fds[i], serv_fd_set);  
      if(*max_fd < client_fds[i]){  
      	*max_fd = client_fds[i];
        printf("update max_fd=%dn",*max_fd);   
      }  
    }
	} 	
}

static void handle_new_connect(fd_set* serv_fd_set,int listen_fd)
{
	int i;	
  if(FD_ISSET(listen_fd, serv_fd_set)){
  	//有新的连接请求 
  	struct sockaddr_in client_address = {0};  
    socklen_t address_len = sizeof(client_address); 
    int client_sock_fd = accept(listen_fd, (struct sockaddr *)&client_address, &address_len);
    printf("new connection client_sock_fd = %dn", client_sock_fd);
    
    if(client_sock_fd > 0){  
      int index = -1;  
      for(i = 0; i < MAX_CONNECTED_NO; i++){  
        if(client_fds[i] == 0){  
          index = i;  
          client_fds[i] = client_sock_fd;  
          break;  
        }
      }  
      if(index >= 0){  
        printf("new client(%d)add success,%s:%dn",index, 
        		inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port));	
        show_socket_info(client_sock_fd);			
      }  
      else{  
        bzero(input_msg, BUFFER_SIZE);  
        strcpy(input_msg,"client amount is the maxium,can't add!n");  
        send(client_sock_fd, input_msg, BUFFER_SIZE, 0);  
        printf("client amount is the maxium,new client joined fail %s:%dn",
        	inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  
      }   
    }
  }
}

static void broadcast_stdin(fd_set* serv_fd_set)
{
	int i;	
  if(FD_ISSET(STDIN_FILENO, serv_fd_set)){ 
    bzero(input_msg, BUFFER_SIZE);
    ssize_t size = read(STDIN_FILENO, input_msg, sizeof(input_msg));
    
    printf("stdinput msg:%sn",input_msg); 
    //输入“.quit"则退出服务器  
    if(strcmp(input_msg, QUIT_CMD) == 0){  
    	exit(0);  
    }
    
    for(i = 0; i < MAX_CONNECTED_NO; i++){  
    	if(client_fds[i] != 0){  
      	printf("send to client_fds[%d]=%dn", i, client_fds[i]);   
		    if(send(client_fds[i], input_msg, size, 0) == -1){  
		    	perror("send error!n");  
		    }else
		    	printf("send ok n");        	
      }  
    }
  } 
}

static void forward_from_recv(fd_set* serv_fd_set)
{
	int i;
  for(i = 0; i < MAX_CONNECTED_NO; i++){
		if(client_fds[i] !=0){
			if(FD_ISSET(client_fds[i], serv_fd_set)){
				//处理某个客户端过来的消息  
	      bzero(recv_msg, BUFFER_SIZE);  
	      int byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0); 
	      if (byte_num > 0){  
	        if(byte_num > BUFFER_SIZE){  
	        	byte_num = BUFFER_SIZE;
	        	recv_msg[byte_num] = '';
	        }  
	        printf("recv from client(%d),msg:%s n",i,recv_msg);
	        /*转发数据给其他的客户端*/                         
	        for(int i = 0; i < MAX_CONNECTED_NO; i++){  
	          if(client_fds[i] != 0){   
	          	send(client_fds[i], recv_msg, sizeof(recv_msg), 0);  
	          }
	        }    
	      }  
	      else if(byte_num < 0){  
	      	printf("get msg error from client(%d).n", i);  
	      }  
	      else{  
	        FD_CLR(client_fds[i], serv_fd_set);  
	        client_fds[i] = 0;  
	        printf("client(%d) is quittedn", i);  
	      }  
			}
		}
	}
}

static void check_event(fd_set* serv_fd_set,int* max_fd,int listen_fd)
{
	struct timeval tv;  //超时时间设置 
	int ret = -1; 
	tv.tv_sec = 20;  
	tv.tv_usec = 0;

	//dump_data("fd check",(char*)serv_fd_set,128);
	//printf("max_fd:%dn",*max_fd);		
  ret = select(*max_fd + 1, serv_fd_set, NULL, NULL, &tv);  
  if(ret < 0){  
    perror("select errorn");  
    return;  
  }  
  else if(ret == 0){  
    printf("select tmon");  
    return;
  }  
  else{
  	//ret 为未状态发生变化的文件描述符的个数
  	printf("ret:%d n",ret);
  	handle_new_connect(serv_fd_set,listen_fd); 
  	broadcast_stdin(serv_fd_set);
   	forward_from_recv(serv_fd_set);
  } 	
}

#ifdef DUMP_DATA
static void dump_data(char* name,char* buf,int len)
{
	int i;
	printf("%s:",name);
	for(i=0; i<len; i++){
		if(i%8 == 0)
			printf("n");
		printf("%4d ",buf[i]);	
	}
	printf("n");	
}
#endif

static void show_socket_info(int listen_fd)
{
	struct sockaddr_in *tmp_sockaddr;
	struct sockaddr connectedAddr;

	int ret = -1;
	
	socklen_t connectedAddrLen = sizeof(connectedAddr);
	//获取listen_fd表示的连接上的本地地址
	ret = getsockname(listen_fd, (struct sockaddr *)&connectedAddr, &connectedAddrLen);
  if(ret){
  	perror("getsockname");
  	return;
  }	
	tmp_sockaddr = (struct sockaddr_in *)&connectedAddr;
	printf("socket address = %s:%dn", inet_ntoa(tmp_sockaddr->sin_addr), ntohs(tmp_sockaddr->sin_port));		
}

static void show_peer_info(int listen_fd)
{	
	struct sockaddr_in peerAddr;	
	char ipAddr[INET_ADDRSTRLEN];//保存点分十进制的地址
	socklen_t peerLen;
	int ret = -1;
	//获取listen_fd表示的连接上的对端地址
  ret = getpeername(listen_fd, (struct sockaddr *)&peerAddr, &peerLen); 
  if(ret){
  	perror("getpeername");
  	return;
  }
 	
  printf("socket peer address = %s:%dn", inet_ntop(AF_INET,&(peerAddr.sin_addr), ipAddr, sizeof(ipAddr)), 
  		ntohs(peerAddr.sin_port));	
}

使用epoll接口实现的多台client与一台server之间的通讯

epoll的性能较select更优,结合线程池方式能够处理海量并发连线事件。但epoll对应的接口都抽象为统一的读、写等等统一接口,各个epoll对象之间的状态改变比较晦涩,需要有透彻的理解才可以。

客户端代码:

#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define MAXSIZE     1024
#define SERVPORT   	4444
#define FDSIZE        1024
#define EPOLLEVENTS 20

static void handle_connection(int sockfd);
static void
handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf);
static void do_read(int epollfd,int fd,int sockfd,char *buf);
static void do_write(int epollfd,int fd,int sockfd,char *buf);
static void add_event(int epollfd,int fd,int state);
static void delete_event(int epollfd,int fd,int state);
static void modify_event(int epollfd,int fd,int state);

static void setnonblocking(int sock)
{
	int opts;
	opts=fcntl(sock,F_GETFL);
	if(opts<0){
		perror("fcntl(sock,GETFL)");
		return;
	}
	opts = opts|O_NONBLOCK;
	if(fcntl(sock,F_SETFL,opts)<0){
		perror("fcntl(sock,SETFL,opts)");
		return;
	}
}

int main(int argc,char *argv[])
{
    int                 sockfd;
    struct sockaddr_in  servaddr;
    struct hostent *host;
		struct sockaddr_in clientAddr;//客户端地址
  	char ipAddress[INET_ADDRSTRLEN];//保存点分十进制的ip地址
		socklen_t clientAddrLen = sizeof(clientAddr);
	  char **pptr;
	    
    char str[6] = {0,};
		if(argc < 2){
			fprintf(stderr,"Please enter the server's hostname!n");
			exit(1);
		}
		
		/*地址解析函数*/
		if((host=gethostbyname(argv[1]))==NULL){
			perror("gethostbyname");
			exit(1);
		}
		
		strcpy(str,argv[2]);	//第二个参数为client名称	    
    
    sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    
		/*设置sockaddr_in 结构体中相关参数*/
		servaddr.sin_family=AF_INET;
		servaddr.sin_port=htons(SERVPORT);
		servaddr.sin_addr=*((struct in_addr *)host->h_addr);
		bzero(&(servaddr.sin_zero),8);    
		/*调用connect函数主动发起对服务器端的连接*/
		if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr))== -1){
			perror("connect");
			exit(1);
		}
		
		//setnonblocking(sockfd);
		getsockname(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);//获取sockfd表示的连接上的本地地址
	  printf("client address = %s:%dn", inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress)), 
	  	ntohs(clientAddr.sin_port));

		printf("official hostname:%sn", host->h_name);   //主机规范名
		for(pptr = host->h_aliases; *pptr != NULL; pptr++)   //将主机别名打印出来
			printf("alias: %sn", *pptr);	

		printf("client sockfd:%d n",sockfd);
	    
    //处理连接
    //while(1)
    sleep(1);
    handle_connection(sockfd);
    close(sockfd);
    return 0;
}


static void handle_connection(int sockfd)
{
    int epollfd;
    struct epoll_event events[EPOLLEVENTS];
    char buf[MAXSIZE];
    int ret;
    epollfd = epoll_create(FDSIZE);
    add_event(epollfd,STDIN_FILENO,EPOLLIN);
    add_event(epollfd,sockfd,EPOLLIN);
    for ( ; ; )
    {
        ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
        handle_events(epollfd,events,ret,sockfd,buf);
    }
    close(epollfd);
}

static void
handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf)
{
    int fd;
    int i;
    //printf("sockfd=%d n",sockfd);
    for (i = 0;i < num;i++)
    {
        fd = events[i].data.fd;
        //printf("fd=%d n",fd);
        if (events[i].events & EPOLLIN)
            do_read(epollfd,fd,sockfd,buf);
        else if (events[i].events & EPOLLOUT)
            do_write(epollfd,fd,sockfd,buf);
    }
}

static void do_read(int epollfd,int fd,int sockfd,char *buf)
{
    int nread;
    nread = read(fd,buf,MAXSIZE);
    if (nread == -1)
    {
        perror("read error:");
        close(fd);
    }
    else if (nread == 0)
    {
        fprintf(stderr,"server close.n");
        close(fd);
    }
    else
    {
        if (fd == STDIN_FILENO){
        		printf("client send to %d n",sockfd);
            modify_event(epollfd,sockfd,EPOLLOUT);
        } 
        else{
        		printf("read from server n");
            //modify_event(epollfd,sockfd,EPOLLIN);
            add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
        }
    }
}

static void do_write(int epollfd,int fd,int sockfd,char *buf)
{
    int nwrite;
    printf("client writr to %d,str:%s n",fd,buf);
    nwrite = write(fd,buf,strlen(buf));
    
    if (nwrite == -1)
    {
        perror("write error:");
        close(fd);
    }
    else
    {
        if (fd == STDOUT_FILENO)
            delete_event(epollfd,fd,EPOLLOUT);
        else
            modify_event(epollfd,fd,EPOLLIN);
    }
    memset(buf,0,MAXSIZE);
}

static void add_event(int epollfd,int fd,int state)
{
    struct epoll_event ev;
    ev.events = state|EPOLLET;
    ev.data.fd = fd;
    printf("EPOLL_CTL_ADD fd:%d state:%dn",fd,state);
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
}

static void delete_event(int epollfd,int fd,int state)
{
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    printf("EPOLL_CTL_DEL fd:%d state:%dn",fd,state);
    epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
}

static void modify_event(int epollfd,int fd,int state)
{
    struct epoll_event ev;
    ev.events = state|EPOLLET;
    ev.data.fd = fd;
    printf("EPOLL_CTL_MOD fd:%d state:%dn",fd,state);
    epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
}

解读一下client端,do_read/do_write函数中的几个epoll接口的引用:

do_read 

if (fd == STDIN_FILENO){
        printf("client send to %d n",sockfd);
        modify_event(epollfd,sockfd,EPOLLOUT);
 } 
else{
        printf("read from server n");
        add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
}

 do_write

if (fd == STDOUT_FILENO)
        delete_event(epollfd,fd,EPOLLOUT);
else
        modify_event(epollfd,fd,EPOLLIN);

  1. do_read,当监控到stdin输入时,将sockfd变为EPOLLOUT(输出)状态,以执行将键盘输入发送给server端。
  2. 此后会触发do_write,在将数据发送给server后,将fd的状态改回EPOLLIN(监控输入)状态。
  3. do_read,当监控到server socket发送过来的数据时,需要增加一个STDOUT_FILENO的标准输出(EPOLLOUT),来讲数据显示到屏幕上。
  4. 此后会触发do_write,将数据写到屏幕上后,再删除该标准输出STDOUT_FILENO的epoll。

总结来看:

  1. 当一个fd的epoll已经被add过,再执行add(EPOLL_CTL_ADD)操作 ,此后并不会触发add指定的读/写操作,而应该使用modify(EPOLL_CTL_MOD)。
  2. 同理,某些fd的epoll也可以采用先delete(EPOLL_CTL_DEL),再add(EPOLL_CTL_ADD)操作也可以。

 

服务端代码,服务端引用了网络上线程池的代码,待深入了解。

#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
  
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 4444
#define INFTIM 1000
  
//线程池任务队列结构体
struct task{
  int fd; //需要读写的文件描述符
  struct task *next; //下一个任务
};
  
//用于读写两个的两个方面传递参数
struct user_data{
  int fd;
  unsigned int n_size;
  char line[MAXLINE];
};
  
//线程的任务函数
void * readtask(void *args);
void * writetask(void *args);
  
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[20];
int epfd;
pthread_mutex_t mutex;
pthread_cond_t cond1;
struct task *readhead=NULL,*readtail=NULL,*writehead=NULL;
  
static void setnonblocking(int sock)
{
	int opts;
	opts=fcntl(sock,F_GETFL);
	if(opts<0){
		perror("fcntl(sock,GETFL)");
		return;
	}
	opts = opts|O_NONBLOCK;
	if(fcntl(sock,F_SETFL,opts)<0){
		perror("fcntl(sock,SETFL,opts)");
		return;
	}
}
  
int main()
{
	int i, maxi, listenfd, connfd, sockfd,nfds;
	pthread_t tid1,tid2;

	struct task *new_task=NULL;
	//struct user_data *rdata=NULL;
	socklen_t clilen;

	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond1,NULL);
	//初始化用于读线程池的线程
	pthread_create(&tid1,NULL,readtask,NULL);
	pthread_create(&tid2,NULL,readtask,NULL);

	listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	
	//生成用于处理accept的epoll专用的文件描述符
	epfd=epoll_create(256);

	struct sockaddr_in clientaddr;
	struct sockaddr_in serveraddr;
	//把socket设置为非阻塞方式

	//setnonblocking(listenfd);
	//设置与要处理的事件相关的文件描述符
	ev.data.fd=listenfd;
	//设置要处理的事件类型
	ev.events=EPOLLIN|EPOLLET;
	//注册epoll事件
	epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port=htons(SERV_PORT);
	serveraddr.sin_addr.s_addr = INADDR_ANY;
	bind(listenfd,(struct sockaddr *)&serveraddr, sizeof(serveraddr));
	listen(listenfd, LISTENQ);

	printf("server listenfd=%d n",listenfd);
  //处理连接
  //while(1)
  sleep(1);
    		
	maxi = 0;
	for ( ; ; ) {
		//等待epoll事件的发生
		nfds = epoll_wait(epfd,events,20,500);
		
		//printf("trigered event num=%d n",nfds);
		//处理所发生的所有事件
		for(i=0;i<nfds;++i){
			if(events[i].data.fd==listenfd){
				connfd = accept(listenfd,(struct sockaddr *)&clientaddr, &clilen);
				if(connfd<0){
	  			perror("connfd<0");
	  			return 0;
				}
				setnonblocking(connfd);
				
        printf("new connection connfd = %dn", connfd);
	      printf("new client add success,%s:%dn",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));  
 
				//设置用于读操作的文件描述符
				ev.data.fd=connfd;
				//设置用于注测的读操作事件
				ev.events=EPOLLIN|EPOLLET;
				//注册ev
				epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
			}
			else if(events[i].events&EPOLLIN){
				printf("reading event[%d].data.fd=%d/n",i,events[i].data.fd);
				if((sockfd = events[i].data.fd) < 0) 
					continue;
				new_task = malloc(sizeof(struct task));	
				//new_task=new task();
				new_task->fd=sockfd;
				new_task->next=NULL;
				//添加新的读任务
				pthread_mutex_lock(&mutex);
				if(readhead==NULL){
				  readhead=new_task;
				  readtail=new_task;
				}
				else{
				 	readtail->next=new_task;
				  readtail=new_task;
				}
				//唤醒所有等待cond1条件的线程
				pthread_cond_broadcast(&cond1);
				pthread_mutex_unlock(&mutex);
			}
			else if(events[i].events&EPOLLOUT){
				printf("writing event[%d].data.fd=%d/n",i,events[i].data.fd);
			/*
			rdata=(struct user_data *)events[i].data.ptr;
			sockfd = rdata->fd;
			write(sockfd, rdata->line, rdata->n_size);
			delete rdata;
			//设置用于读操作的文件描述符
			ev.data.fd=sockfd;
			//设置用于注测的读操作事件
			ev.events=EPOLLIN|EPOLLET;
			//修改sockfd上要处理的事件为EPOLIN
			epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
			*/
			}          
		}
	}
}

static int count111 = 0;
static time_t oldtime = 0, nowtime = 0;
void * readtask(void *args)
{
	int fd=-1;
	unsigned int n = 0;
	//用于把读出来的数据传递出去
	struct user_data *data = NULL;
	while(1){
		pthread_mutex_lock(&mutex);
		//等待到任务队列不为空

		while(readhead==NULL)
			pthread_cond_wait(&cond1,&mutex);

		fd=readhead->fd;
		//从任务队列取出一个读任务
		struct task *tmp=readhead;
		readhead = readhead->next;
		//delete tmp;
		free(tmp);
		tmp = NULL;
		pthread_mutex_unlock(&mutex);
		//data = new user_data();
		data = malloc(sizeof(struct user_data));
		data->fd=fd;

		char recvBuf[1024] = {0};
		int ret = 999;
		int rs = 1;

		while(rs){
			ret = recv(fd,recvBuf,1024,0);// 接受客户端消息

			if(ret < 0){
				//由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可//读在这里就当作是该次事件已处理过。
				if(errno == EAGAIN){
					printf("EAGAINn");
					break;
				}
				else{
					printf("recv error!n");
					close(fd);
					break;
				}
			}
			else if(ret == 0){
				// 这里表示对端的socket已正常关闭.
				rs = 0;
			}
			if(ret == sizeof(recvBuf))
				rs = 1; // 需要再次读取
			else
				rs = 0;
		}
		if(ret>0){
			//-------------------------------------------------------------------------------
			data->n_size=n;
			count111 ++;
			struct tm *today;
			time_t ltime;
			time( &nowtime );

			if(nowtime != oldtime){
				printf("%dn", count111);
				oldtime = nowtime;
				count111 = 0;
			}

			char buf[1000] = {0};
			sprintf(buf,"HTTP/1.0 200 OKrnContent-type: text/plainrnrn%s","Hello world!n");
			send(fd,buf,strlen(buf),0);
			//close(fd);
		}
	}
}