在类Unix平台实现TCP服务端

在类Unix平台实现TCP客户端

创建服务器socket

在TCP服务器代码中,我们创建一个socket,然后调用bind函数,绑定到这个socket:

	// 创建本地地址配置信息
	struct addrinfo hints;
	// 清空hints的东西,为设置新的信息做准备
	memset(&hints,0,sizeof(hints));
	// TCP,选用SOCK_STREAM
	hints.ai_socktype = SOCK_STREAM;
	// 因为此地址将用作监听socket的地址,因此设置被动状态
	hints.ai_flags = AI_PASSIVE;
	// 返回的本地地址信息
	struct addrinfo *local_address;
	// getaddrinfo函数初始化本地地址信息,端口设置为8899
	if(getaddrinfo(NULL,"8899",&hints,&local_address)){
		fprintf(stderr,"getaddrinfo() failed. (%d)\n",errno);
		return 1;
	}

	printf("Creating a socket...\n");
	// 使用配置好的本地地址信息创建socket
	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;
	}

我们没有指定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_fds
		memcpy(&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;
		}
		// 来到这一步,说明有变化了,遍历一遍socket
		for(int i = 0;i <= fdmax;i++){
			// 检查socket是否在返回的读文件描述符集合中
			if(FD_ISSET(i,&read_fds)){
				// 如果在读文件描述符集合中,那么看看是否是监听用的socket,即服务端socket
				if(i == server_socket){
				// 看来是有新的访问要建立连接,处理新连接
					// 记录对端(客户端)socket的信息
					struct sockaddr_storage peer_address;
					socklen_t peer_address_size = sizeof(peer_address);
					// accept函数创建对端(客户端)socket
					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;
					}
					// 将客户端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 client
						printf("Terminated connection on descriptor %d\n",i);
						// 将客户端socket从master_set中移除
						FD_CLR(i,&master_set);
						// 在服务端这边关闭客户端socket
						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);
					
				}
			}
		}
	}

聊天室

					///
					//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++){
	// 检查socket中在不在master_set
	if(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 connection
					struct 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 client
					char read[1024];
					int bytes_read = recv(i,read,sizeof(read),0);
					if(bytes_read <= 0){
						//connection closed by client
						printf("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;
}

相关推荐

  1. Unix平台实现TCP服务

    2024-04-07 07:08:03       42 阅读
  2. Unix平台实现TCP客户

    2024-04-07 07:08:03       32 阅读
  3. tcp服务,时刻线

    2024-04-07 07:08:03       37 阅读
  4. netty的TCP服务和客户实现

    2024-04-07 07:08:03       50 阅读
  5. 【Socket】Unix环境下搭建局域网内TCP服务

    2024-04-07 07:08:03       58 阅读
  6. TCP/IP C 语言实现单个客户服务 TCP 通信

    2024-04-07 07:08:03       47 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-07 07:08:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-07 07:08:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-07 07:08:03       87 阅读
  4. Python语言-面向对象

    2024-04-07 07:08:03       96 阅读

热门阅读

  1. 识别语序成语的简单神经网络

    2024-04-07 07:08:03       35 阅读
  2. Android10以上版本调用相机拍照

    2024-04-07 07:08:03       35 阅读
  3. JVM总结

    2024-04-07 07:08:03       33 阅读