在类Unix平台实现TCP客户端
创建服务器socket
在TCP服务器代码中,我们创建一个socket,然后调用bind函数,绑定到这个socket:
// 创建本地地址配置信息struct addrinfo hints;// 清空hints的东西,为设置新的信息做准备memset(&hints,0,sizeof(hints));// TCP,选用SOCK_STREAMhints.ai_socktype = SOCK_STREAM;// 因为此地址将用作监听socket的地址,因此设置被动状态hints.ai_flags = AI_PASSIVE;// 返回的本地地址信息struct addrinfo *local_address;// getaddrinfo函数初始化本地地址信息,端口设置为8899if(getaddrinfo(NULL,"8899",&hints,&local_address)){fprintf(stderr,"getaddrinfo() failed. (%d)\n",errno);return 1;}printf("Creating a socket...\n");// 使用配置好的本地地址信息创建socketint server_socket = socket(local_address->ai_family,local_address->ai_socktype,local_address->ai_protocol);if(server_socket == -1){fprintf(stderr,"socket() failed. (%d)\n",errno);return 1;}
我们没有指定hints.ai_family = AF_INET或 AF_INET6,因为getaddrinfo可以动态决定它的具体类型,也就是我们可以同时兼容IPv4 和IPv6.
绑定socket到一个本地地址
上一步只是用本地地址信息创建了socket,还要将这个socket与本地地址绑定起来,才能真正关联起来。由bind函数来完成
printf("Binding socket to local address...\n");if(bind(server_socket,local_address->ai_addr,local_address->ai_addrlen)){fprintf(stderr,"bind() failed. (%d)\n",errno);return 1;}// 绑定完成后,本地地址信息后面就不会再使用了,于是我们释放掉它,节省内存freeaddrinfo(local_address);
让socket进入监听状态
调用listen函数让socket可以监听外界对它的访问。我们这里设置了最多有10个等待处理的进来的访问,换句话说,
printf("Listening...\n");if(listen(server_socket,10) == -1){fprintf(stderr,"listen() failed. (%d)\n",errno);return 1;}
使用select函数处理socket的同步
这一段是最精彩的。
// 初始化fd_set文件描述符集合fd_set master_set;// 清空文件描述符集合里的东西FD_ZERO(&master_set);// 将刚刚创建的用于监听的socket加入到集合中,当这个socket需要处理进来的请求时,就可以select函数中返回FD_SET(server_socket,&master_set);int fdmax;fdmax = server_socket;// 无限循环,这是正常的,因它是主程序,不能够退出。while(1){// 定义一个读文件描述符集合fd_set read_fds;// 清0集合FD_ZERO(&read_fds);// 将master_set复制一份到read_fdsmemcpy(&read_fds,&master_set,sizeof(master_set));// select函数将read_fds传进去,第一个参数是最大socket编号加1,read_fds包含所有需要监听读变化的文件描述符,如果有变化就会通过read_fds返回(只包含有读变化的socket,进去是全部socket,出来时只有部分),所以前面需要将master_set复制一份到read_fds。select是一个阻塞的函数,除非有变化,否则就卡在这了,这样省了很多资源的。if(select(fdmax+1,&read_fds,0,0,0) == -1){fprintf(stderr,"select() failed. (%d)\n",errno);return 1;}// 来到这一步,说明有变化了,遍历一遍socketfor(int i = 0;i <= fdmax;i++){// 检查socket是否在返回的读文件描述符集合中if(FD_ISSET(i,&read_fds)){// 如果在读文件描述符集合中,那么看看是否是监听用的socket,即服务端socketif(i == server_socket){// 看来是有新的访问要建立连接,处理新连接// 记录对端(客户端)socket的信息struct sockaddr_storage peer_address;socklen_t peer_address_size = sizeof(peer_address);// accept函数创建对端(客户端)socketint peer_socket = accept(server_socket,(struct sockaddr*)&peer_address,&peer_address_size);if(peer_socket == -1){fprintf(stderr,"accept() failed. (%d)\n",errno);return 1;}// 将客户端socket放入文件描述符集合中,以便与其通信FD_SET(peer_socket,&master_set);if(peer_socket > fdmax){fdmax = peer_socket;}char address_buffer[100];// 打印客户端的信息getnameinfo((struct sockaddr*)&peer_address,peer_address_size,address_buffer,sizeof(address_buffer),0,0,NI_NUMERICHOST);printf("Accepted connection on descriptor %d (host=%s)\n",peer_socket,address_buffer);}else{//处理客户端来的信息char read[1024]; // 准备一个接收信息字符数组// 接收客户端的数据int bytes_read = recv(i,read,sizeof(read),0);if(bytes_read <= 0){// 如果接收到数据小于或等于0,那么意味着客户端要关闭//connection closed by clientprintf("Terminated connection on descriptor %d\n",i);// 将客户端socket从master_set中移除FD_CLR(i,&master_set);// 在服务端这边关闭客户端socketclose(i);continue;}printf("Received message (%d bytes): %s\n",bytes_read,read);/////echo message back to clientfor (int j = 0; j < bytes_read; j++){read[j] = toupper(read[j]);}printf("Sending message (%d bytes): %s\n",bytes_read,read);// 向客户端发送数据send(i,read,bytes_read,0);}}}}
聊天室
/////echo message back to clientfor (int j = 0; j < bytes_read; j++){read[j] = toupper(read[j]);}printf("Sending message (%d bytes): %s\n",bytes_read,read);// 向客户端发送数据send(i,read,bytes_read,0);
将上述代码,用下面的代码代替,就可以将程序变成聊天室。聊天室就要将信息发给每一个客户端,服务端和自己是不需要收到发的信息的。
for(int j = 0; j <= fdmax; j++){// 检查socket中在不在master_setif(FD_ISSET(j,&master_set)){// 如果是服务端socket则进入下一个if(j == server_socket){continue;}// 如果是自己,即同一个socket则进入下一轮if(j == i){continue;}// 给其他客户端发信息send(j,read,bytes_read,0);}
}
完整代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>int main(int argc,char *argv[]){printf("Configuring local address...\n");struct addrinfo hints;memset(&hints,0,sizeof(hints));hints.ai_socktype = SOCK_STREAM;hints.ai_flags = AI_PASSIVE;struct addrinfo *local_address;if(getaddrinfo(NULL,"8080",&hints,&local_address)){fprintf(stderr,"getaddrinfo() failed. (%d)\n",errno);return 1;}printf("Creating a socket...\n");int server_socket = socket(local_address->ai_family,local_address->ai_socktype,local_address->ai_protocol);if(server_socket == -1){fprintf(stderr,"socket() failed. (%d)\n",errno);return 1;}printf("Binding socket to local address...\n");if(bind(server_socket,local_address->ai_addr,local_address->ai_addrlen)){fprintf(stderr,"bind() failed. (%d)\n",errno);return 1;}freeaddrinfo(local_address);printf("Listening...\n");if(listen(server_socket,10) == -1){fprintf(stderr,"listen() failed. (%d)\n",errno);return 1;}fd_set master_set;FD_ZERO(&master_set);FD_SET(server_socket,&master_set);int fdmax;fdmax = server_socket;while(1){fd_set read_fds;FD_ZERO(&read_fds);memcpy(&read_fds,&master_set,sizeof(master_set));if(select(fdmax+1,&read_fds,0,0,0) == -1){fprintf(stderr,"select() failed. (%d)\n",errno);return 1;}for(int i = 0;i <= fdmax;i++){if(FD_ISSET(i,&read_fds)){if(i == server_socket){//handle new connectionstruct sockaddr_storage peer_address;socklen_t peer_address_size = sizeof(peer_address);int peer_socket = accept(server_socket,(struct sockaddr*)&peer_address,&peer_address_size);if(peer_socket == -1){fprintf(stderr,"accept() failed. (%d)\n",errno);return 1;}FD_SET(peer_socket,&master_set);if(peer_socket > fdmax){fdmax = peer_socket;}char address_buffer[100];getnameinfo((struct sockaddr*)&peer_address,peer_address_size,address_buffer,sizeof(address_buffer),0,0,NI_NUMERICHOST);printf("Accepted connection on descriptor %d (host=%s)\n",peer_socket,address_buffer);}else{//handle data from clientchar read[1024];int bytes_read = recv(i,read,sizeof(read),0);if(bytes_read <= 0){//connection closed by clientprintf("Terminated connection on descriptor %d\n",i);FD_CLR(i,&master_set);close(i);continue;}printf("Received message (%d bytes): %s\n",bytes_read,read);//echo message back to client// for (int j = 0; j < bytes_read; j++)// {// read[j] = toupper(read[j]);// }// printf("Sending message (%d bytes): %s\n",bytes_read,read);// send(i,read,bytes_read,0);for(int j = 0; j <= fdmax; j++){if(FD_ISSET(j,&master_set)){if(j == server_socket){continue;}if(j == i){continue;}send(j,read,bytes_read,0);}}}}}}printf("Closing socket\n");close(server_socket);printf("Finished.\n");return 0;
}