Linux-C TCP简单例子

一、简述

       记-使用TCP协议通信的简单例子。

       例子1:一个客户端,一个服务端,客户端发送信息,服务端就收信息。

       例子2:使用多线程实现  服务器与客户端的双向通信。

       例子3:使用多路复用实现 服务器与客户端的双向通信 。

       例子4:多个客户端,一个服务器,客户端发送信息,服务端就收信息。

       例子5:使用多线程实现  服务器与多个客户端的双向通信。

       例子6:使用多路复用实现  服务器与多个客户端的双向通信。   

       代码打包:链接: https://pan.baidu.com/s/1x-JnmAm-4yqu54SoUqr20A 提取码: sxt5 

二、TCP基本操作。

     说明:  网络中有很多主机,我们使用IP地址标识主机的身份。一台主机有多个应用程序,我们使用端口号来标识应用。

       端口号范围0~65535,其中0~1024一般给系统使用,或者被知名软件所注册使用,所以为了避免冲突,开发时一般使用1025~65535。当主机某个端口收到消息,只有注册该端口(或者说绑定该端口)的应用才会收到消息。一般一个端口同时只能被一个应用所使用。

     补充:占用端口的程序异常退出之后,系统回收端口会有一定时间,此时再次绑定同一个端口会出现,端口被占用的情况。

                                            

     解决办法:方法1) 换一个端口    方法2)在代码中设置端口复用

    //设置端口复用的属性  
	int on = 1; //1开启 0关闭
    int ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));	
	if(ret < 0)
	{
		perror("setsockopt fail\n");
		return -1;
	}

     基本操作步骤:

服务器端:创建tcp监听scoket,绑定地址,accept()接受客户端连接,返回一个与客户端通信的socket。接收数据使用read(),

                 发送数据用write();使用close()关闭通信。

客户端:创建tcp通信socket,直接向指定的服务器地址发起连接请求,连接成功后,接收数据使用read(),发送数据用write();

                使用close()关闭通信。

server端

//1 创建TCP通信socket
    int tcp_socket_fd = socket(AF_INET,SOCK_STREAM,0);//AF_INET:标识IPv4协议,IPv6是AF_INET6;SOCK_STREAM标识是TCP协议,UDP是SOCK_DGRAM;0表示默认

//2 绑定IP地址
	struct sockaddr_in local_addr = {0};
	local_addr.sin_family = AF_INET;//使用IPv4
	local_addr.sin_port = htons(port));//port是端口号,网络传输使用大端字节序,使用htons()进行转换。端口是int类型,范围0~65535
	local_addr.sin_addr.s_addr = INADDR_ANY;  //自动检测本地网卡设备并绑定IP
	
	bind(tcp_socket_fd,(struct sockaddr *)&local_addr,sizeof(local_addr));//绑定socket的IP地址

// 3 设置监听队列(最多可以连接多少个客户端)
    listen(tcp_socket_fd,5);

//4 等待,接受客户端的链接
    struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
	int len = sizeof(client_addr);//地址信息长度
	int new_socket = 0;//与客户端通信的socket
    new_socket  = accept(tcp_socket_fd,(struct sockaddr *)&client_addr,&len);//阻塞,等待客户端的连接
	
//5 关闭通信socket
    close(new_socket);
    close(tcp_socket_fd);

client端

//1 创建TCP通信socket
    int tcp_socket_fd = socket(AF_INET,SOCK_STREAM,0);

//2 连接服务器
    struct sockaddr_in server_addr = {0};//要连接服务器的地址
    server_addr.sin_family = AF_INET;//使用IPv4
	server_addr.sin_port = htons(port);//指定要连接服务器的某个端口, 端口是int类型,范围0~65535
	server_addr.sin_addr.s_addr = inet_addr(ip);//服务器的ip, ip是字符串类型,要进行相应的转换
	
	connect(tcp_socket_fd ,(struct sockaddr *)&server_addr,sizeof(server_addr));//发起连接服务器请求
	
//3 关闭通信socket
    close(tcp_socket_fd);
socket()函数
功能创建通信端点(可以创建TCP通信端点、UDP通信端点)
头文件#include <sys/types.h>          
#include <sys/socket.h>
原型int socket(int domain, int type, int protocol);
参数domain通信协议簇(包含IPv4,IPv6),详细请看<sys/socket.h>
type

通信协议类型(包含TCP,UDP),更多请查询手册: man socket

SOCK_STREAM:表示TCP

SOCK_DGRAM:表示UDP

 protocol指定与套接字一起使用的特定协议。通常,只有一个协议支持给定协议族中的特定套接字类型,在这种情况下协议可以指定为0.但是,可能存在许多协议,在这种情况下,必须在此指定特定协议方式。
返回值

成功:返回一个文件描述符。

失败:返回-1,并设置errno

备注详细请查看man手册:man 2 socket

 

bind()函数
功能将IP地址信息绑定到socket
头文件#include <sys/types.h>          
#include <sys/socket.h>
原型int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数sockfd通信socket
addr

要绑定的地址信息(包括IP地址,端口号)

通用地址结构体定义:

struct sockaddr {
               sa_family_t sa_family;//地址族, AF_xxx
               char        sa_data[14];//包括IP和端口号
}

新型的IP地址结构体:(查看新型的结构体信息: gedit  /usr/include/linux/in.h )

struct sockaddr_in {
  __kernel_sa_family_t    sin_family;    /*地址族,IP协议*/  默认:AF_INET
  __be16        sin_port;            /*端口号*/
  struct in_addr    sin_addr;        /*网络IP地址*/


  unsigned char        __pad //8位的预留接口
};

 addrlen地址信息大小
返回值

成功:返回0。

失败:返回-1,并设置errno

备注详细请查看man手册:man 2 bind
connect()函数
功能发起连接请求
头文件#include <sys/types.h>          
#include <sys/socket.h>
原型int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数sockfd通信socket
addr

要连接的服务器地址

 addrlen地址信息大小
返回值

成功:返回0。

失败:返回-1,并设置errno

备注详细请查看man手册:man 2 connect

 

htons()函数
功能

将无符号短整型数(unsigned short integer)主机字节顺序转换为网络字节序

(h:host主机,to,n:net网络,s:short短整型)

头文件#include <arpa/inet.h>
原型uint16_t htons(uint16_t hostshort);
参数hostshort无符号短整型数
返回值转换后的值
备注

详细请查看man手册:man 3 htons

相关函数:

uint32_t htonl(uint32_t hostlong);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

inet_addr()函数
功能

将地址cp从IPv4数字和点符号(点分十进制形式)转换为 网络字节顺序的二进制形式。

(cp:char 类型的ip)

头文件#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
原型in_addr_t inet_addr(const char *cp);
参数cpip地址 (如"192.168.21.155")
返回值

成功:转换后的ip值

失败:INADDR_NONE  (-1)

备注

手册中提到:

使用inet_addr()函数是有问题的,因为失败时返回的-1是有效地址(255.255.255.255),应该避免使用。可以使用inet_aton(),inet_pton(3)或getaddrinfo(3),它们提供了一种更清晰的方式来指示错误返回。

详细请查看man手册:man 3 inet_addr

相关函数:

int inet_aton(const char *cp, struct in_addr *inp);

in_addr_t inet_network(const char *cp);

char *inet_ntoa(struct in_addr in);

struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);

in_addr_t inet_lnaof(struct in_addr in);

in_addr_t inet_netof(struct in_addr in);

 

三、例子

例子1:一个客户端,一个服务端,客户端发送信息,服务端接收信息。

效果:

 

server.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> //close()
#include <string.h> //strcmp()等字符串操作函数
#include <stdlib.h> //atoi() 字符串转int

int main(int argc, char *argv[])
{
	//判断命令行参数是否满足
	if(argc != 2)
	{
		printf("请传递一个端口号\n");
		return -1;
	}

	//将接收端口号并转换为int
	int port = atoi(argv[1]);
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}

	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("创建tcp通信socket失败!\n");
		return -1;
	}

	//2 绑定socket地址
	struct sockaddr_in server_addr = {0};//存放地址信息
	server_addr.sin_family = AF_INET;//AF_INET->IPv4  
	server_addr.sin_port = htons(port);//端口号
	server_addr.sin_addr.s_addr = INADDR_ANY;//让系统检测本地网卡,自动绑定本地IP
	int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if(ret == -1)
	{
		perror("bind failed!\n");
		return -1;
	}

	//3 设置监听队列,设置为可以接受5个客户端连接
	ret = listen(socket_fd, 5);
	if(ret == -1)
	{
		perror("listen falied!\n");
	}


	printf("server is running!\n");

	struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
	int len = sizeof(client_addr);
	int new_socket_fd = -1;//存放与客户端的通信socket
	
	//4 等待客户端连接
	new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
	if(new_socket_fd == -1)
	{
		perror("accpet error!\n");
	}
	else
	{
		printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
	}

	//循环接收信息
	while(1)
	{
		char buf[1024] = {0};
		read(new_socket_fd, buf, sizeof(buf));//阻塞,,等待客户端发来消息
		printf("receive msg:%s\n", buf);//打印消息
		if(strcmp(buf, "exit") == 0)
		{
			break;//退出循环
		}
	}

	//5 关闭socket
	close(new_socket_fd);
	close(socket_fd);

	return 0;
}

client.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> //close()
#include <string.h> //strcmp()等字符串操作函数
#include <stdlib.h> //atoi() 字符串转int

int main(int argc, char *argv[])
{
	//检查命令行参数是否匹配
	if(argc != 3)
	{
		printf("请传递要连接的服务器的ip和端口号");
		return -1;
	}
	
	int port = atoi(argv[2]);//从命令行获取端口号
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}
	
	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
	}

	//2 连接服务器
	struct sockaddr_in server_addr = {0};//服务器的地址信息
	server_addr.sin_family = AF_INET;//IPv4协议
	server_addr.sin_port = htons(port);//服务器端口号
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
	int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect failed!\n");
	}
	else
	{
		printf("connect server successful!\n");
	}


	//3 循环发送消息
	while(1)
	{
		char buf[1024] = {0};
		printf("Please enter msg:");
		scanf("%s",buf);
		write(socket_fd,buf,strlen(buf));//发送消息
		if(strcmp(buf, "exit") == 0)
		{
			break;//退出循环
		}
	}

	//4 关闭心贴心socket
	close(socket_fd);

	return 0;
}

例子2:使用多线程实现  服务器与客户端的双向通信。

效果:

 

server.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>

void * recv_msg(void *arg);//接收消息函数声明

int main(int argc, char *argv[])
{
	//命令行需要传递一个参数
	if(argc != 2)
	{
		printf("请传递一个端口号\n");
		return -1;
	}

	//从命令行获取端口号
	int port = atoi(argv[1]);
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}
	
	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
		return -1;
	}

	//2 绑定socket地址
	struct sockaddr_in server_addr = {0};
	server_addr.sin_family = AF_INET;//AF_INET->IPv4  
	server_addr.sin_port = htons(port);// server port
	server_addr.sin_addr.s_addr = INADDR_ANY;//server ip (auto set by system)
	int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if(ret == -1)
	{
		perror("bind failed!\n");
		return -1;
	}

	//3 设置监听队列,设置为可以同时连接5个客户端
	ret = listen(socket_fd, 5);
	if(ret == -1)
	{
		perror("listen falied!\n");
		return -1;
	}


	printf("server is running!\n");

	struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
	int len = sizeof(client_addr);
	int new_socket_fd = -1;//存放与客户端的通信socket
	
	//4 等待客户端连接
	new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
	if(new_socket_fd == -1)
	{
		perror("accpet error!\n");
	}
	else
	{
		printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
	}

	//开启接收线程
	pthread_t recv_thread;//存放线程id       recv_msg:线程执行的函数,将通信socket:new_socket_fd传递进去
	ret = pthread_create(&recv_thread, NULL, recv_msg, (void*)&new_socket_fd);
	if(ret != 0)
	{
		printf("开启线程失败\n");
	}
	
	while(1)
	{
		char buf[1024] = {0};
		scanf("%s", buf);
		write(new_socket_fd, buf, sizeof(buf));//发送消息
		
		if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)//退出
		{
			ret = pthread_cancel(recv_thread);//取消线程
			
			break;
		}
	}

	//5 关闭通信socket
	close(new_socket_fd);
	close(socket_fd);

	return 0;
}

//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
	int *socket_fd = (int *)arg;//通信的socket
	while(1)
	{
		char buf[1024] = {0};
		read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
		printf("receive msg:%s\n", buf);
		if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
		{
			//通知主线程。。。
			
			break;//退出
		}
	}
	return NULL;
}

client.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>

void * recv_msg(void *arg);//接收消息函数声明

int main(int argc, char *argv[])
{
	//判断参数个数是否匹配
	if(argc != 3)
	{
		printf("请传递要连接的服务器的IP和端口号\n");
		return -1;
	}
	
	int port = atoi(argv[2]);//从命令行接收参数
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}

	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
	}

	//2 连接服务器
	struct sockaddr_in server_addr = {0};//服务器的地址信息
	server_addr.sin_family = AF_INET;//IPv4协议
	server_addr.sin_port = htons(port);//服务器端口号
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
	int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect failed!\n");
	}
	else
	{
		printf("connect server successful!\n");
	}

	//开启接收线程
	pthread_t recv_thread;//存放线程id       recv_msg:线程执行的函数,将通信socket:new_socket_fd传递进去
	ret = pthread_create(&recv_thread, NULL, recv_msg, (void*)&socket_fd);
	if(ret != 0)
	{
		printf("开启线程失败\n");
	}

	//3 发送消息
	while(1)
	{
		char buf[1024] = {0};//存放要发送的消息
		scanf("%s",buf);
		write(socket_fd,buf,strlen(buf));
		if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
		{
			ret = pthread_cancel(recv_thread);//取消线程
			
			break;
		}
	}

	//4 关闭通信socket
	close(socket_fd);

	return 0;
}


//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
	int *socket_fd = (int *)arg;//通信的socket
	while(1)
	{
		char buf[1024] = {0};
		read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
		printf("receive msg:%s\n", buf);
		if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
		{
			//通知主线程。。。
			
			break;//退出
		}
	}
	return NULL;
}

例子:3:使用多路复用实现 服务器与客户端的双向通信 。 

 效果:刚开始的时候,客户端没有连接,是没有超时现象,也就是select()还没有"工作"。

 

server.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()

int main(int argc, char *argv[])
{
	//命令行需要传递一个参数
	if(argc != 2)
	{
		printf("请传递一个端口号\n");
		return -1;
	}

	//从命令行获取端口号
	int port = atoi(argv[1]);
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}
	
	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
		return -1;
	}

	//2 绑定socket地址
	struct sockaddr_in server_addr = {0};
	server_addr.sin_family = AF_INET;//AF_INET->IPv4  
	server_addr.sin_port = htons(port);// server port
	server_addr.sin_addr.s_addr = INADDR_ANY;//server ip (auto set by system)
	int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if(ret == -1)
	{
		perror("bind failed!\n");
		return -1;
	}

	//3 设置监听队列,设置为可以同时连接5个客户端
	ret = listen(socket_fd, 5);
	if(ret == -1)
	{
		perror("listen falied!\n");
		return -1;
	}


	printf("server is running!\n");

	struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
	int len = sizeof(client_addr);
	int new_socket_fd = -1;//存放与客户端的通信socket
	
	//4 等待客户端连接
	new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
	if(new_socket_fd == -1)
	{
		perror("accpet error!\n");
	}
	else
	{
		printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
	}

	//定义一个文件描述符集合
	fd_set fds;
	//定义一个时间结构体  
	struct timeval  time;
	time.tv_sec = 5;//5秒
	time.tv_usec = 0;

	
	//循环监视文件描述符集合 	
	while(1)
	{
		//清空文件描述符集合
		FD_ZERO(&fds);
		//把标准输入设备加入到集合中 
		FD_SET(0,&fds);
		//把网络通信文件描述符加入到集合中 
		FD_SET(new_socket_fd, &fds);
		
		//select会更新超时参数以指示剩余时间
		time.tv_sec = 3;//3秒超时
		time.tv_usec = 0;
		
		//select会等待time这么久,time可以不设置,即填NULL,那么select会一直阻塞,直到集合中有活跃的描述符
		ret = select(new_socket_fd+1,&fds,NULL,NULL,&time);//阻塞等待time,直到集合中有活跃的描述符
		if(ret < 0)//错误
		{
			perror("select fail:");
			return -1;
		}
		else if(ret == 0) //超时
		{
			printf("timeout\n");
		}
		else if(ret > 0) //有活跃的  ret为1
		{
			//printf("ret=%d\n",ret);
			
			//判断是否是 标准输入设备活跃 假设是则发送数据 
			if(FD_ISSET(0, &fds))//标准输入的描述符是0
			{
				char buf[1024] = {0};
				scanf("%s",buf);
				write(new_socket_fd,buf,strlen(buf));
				if(strcmp(buf, "exit") == 0)
				{
					break;
				}
			}
			
			//判断是否是new_socket_fd活跃,(有消息收到)
			if(FD_ISSET(new_socket_fd,&fds))
			{
				char buf[1024] = {0};
				read(new_socket_fd, buf, sizeof(buf));
				printf("receive msg:%s\n", buf);
				if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
				{
					break;
				}
			}
		}
		printf(".");//用来验证select的阻塞
		
	}

	//5 关闭通信socket
	close(new_socket_fd);
	close(socket_fd);

	return 0;
}

client.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()

int main(int argc, char *argv[])
{
	//判断参数个数是否匹配
	if(argc != 3)
	{
		printf("请传递要连接的服务器的IP和端口号\n");
		return -1;
	}
	
	int port = atoi(argv[2]);//从命令行接收参数
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}

	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
	}

	//2 连接服务器
	struct sockaddr_in server_addr = {0};//服务器的地址信息
	server_addr.sin_family = AF_INET;//IPv4协议
	server_addr.sin_port = htons(port);//服务器端口号
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
	int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect failed!\n");
	}
	else
	{
		printf("connect server successful!\n");
	}

	//定义一个文件描述符集合
	fd_set fds;
	
	//3 发送消息
	while(1)
	{
		//清空文件描述符集合
		FD_ZERO(&fds);
		//把标准输入设备加入到集合中 
		FD_SET(0,&fds);
		//把网络通信文件描述符加入到集合中 
		FD_SET(socket_fd,&fds);
		
		
		ret = select(socket_fd+1,&fds,NULL,NULL,NULL);
		if(ret < 0)//错误
		{
			perror("select fail:");
			return -1;
		}
		else if(ret > 0) //有活跃的
		{
			//判断是否是 标准输入设备活跃 假设是则发送数据
			if(FD_ISSET(0,&fds))
			{
				char buf[1024] = {0};
				scanf("%s",buf);
				write(socket_fd,buf,strlen(buf));
				if(strcmp(buf, "exit") == 0)
				{
					break;
				}
			}

			//判断是否是socket_fd活跃,是则说明有数据收到
			if(FD_ISSET(socket_fd,&fds))
			{
				char buf[1024]={0};
				read(socket_fd,buf,sizeof(buf));
				printf("receive msg:%s\n",buf);
				if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
				{
					break;
				}


			}
		}
		printf("·");//用来验证select的阻塞
	
	}

	//4 关闭通信socket
	close(socket_fd);

	return 0;
}

例子4:多个客户端,一个服务器,客户端发送信息,服务端接收消息。(只能接收最后连接的客户端的消息,因为代码中只处理最新获取的socket)

效果:

 

server.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()

int main(int argc, char *argv[])
{
	//判断命令行参数是否满足
	if(argc != 2)
	{
		printf("请传递一个端口号\n");
		return -1;
	}

	//将接收端口号并转换为int
	int port = atoi(argv[1]);
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}

	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("创建tcp通信socket失败!\n");
		return -1;
	}

	//2 绑定socket地址
	struct sockaddr_in server_addr = {0};//存放地址信息
	server_addr.sin_family = AF_INET;//AF_INET->IPv4  
	server_addr.sin_port = htons(port);//端口号
	server_addr.sin_addr.s_addr = INADDR_ANY;//让系统检测本地网卡,自动绑定本地IP
	int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if(ret == -1)
	{
		perror("bind failed!\n");
		return -1;
	}

	//3 设置监听队列,设置为可以接受5个客户端连接
	ret = listen(socket_fd, 5);
	if(ret == -1)
	{
		perror("listen falied!\n");
	}


	printf("server is running!\n");

	struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
	int len = sizeof(client_addr);
	int new_socket_fd = -1;//存放与客户端的通信socket
	
	//循环接受客户端
	while(1)
	{
		new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);//阻塞,直到有客户端连接,才会向下执行
		if(new_socket_fd == -1)
		{
			perror("accpet error!\n");
		}
		else
		{
			printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
		}
		
		char buf[1024] = {0};
		read(new_socket_fd, buf, sizeof(buf));//接收最新客户端的消息。
		printf("receive msg:%s\n", buf);
		if(strcmp(buf, "exit") == 0)
		{
			break;
		}
	}

	//5 关闭通信socket
	close(new_socket_fd);
	close(socket_fd);

	return 0;
}

clcient.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> //close()
#include <string.h> //strcmp()等字符串操作函数
#include <stdlib.h> //atoi() 字符串转int

int main(int argc, char *argv[])
{
	//检查命令行参数是否匹配
	if(argc != 3)
	{
		printf("请传递要连接的服务器的ip和端口号");
		return -1;
	}
	
	int port = atoi(argv[2]);//从命令行获取端口号
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}
	
	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
	}

	//2 连接服务器
	struct sockaddr_in server_addr = {0};//服务器的地址信息
	server_addr.sin_family = AF_INET;//IPv4协议
	server_addr.sin_port = htons(port);//服务器端口号
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
	int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect failed!\n");
	}
	else
	{
		printf("connect server successful!\n");
	}


	//3 循环发送消息
	while(1)
	{
		char buf[1024] = {0};
		printf("Please enter msg:");
		scanf("%s",buf);
		write(socket_fd,buf,strlen(buf));//发送消息
		if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
		{
			break;//退出循环
		}
	}

	//4 关闭心贴心socket
	close(socket_fd);

	return 0;
}

例子5:使用多线程实现  服务器与多个客户端的双向通信。

(没有检查数组索引越界,在线程退出时没有处理好资源释放)

效果:

 

sever.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>

void * recv_msg(void *arg);//接收消息函数声明
void * send_msg(void *arg);//发送消息函数声明


int main(int argc, char *argv[])
{
	//命令行需要传递一个参数
	if(argc != 2)
	{
		printf("请传递一个端口号\n");
		return -1;
	}

	//从命令行获取端口号
	int port = atoi(argv[1]);
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}
	
	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
		return -1;
	}

	//2 绑定socket地址
	struct sockaddr_in server_addr = {0};
	server_addr.sin_family = AF_INET;//AF_INET->IPv4  
	server_addr.sin_port = htons(port);// server port
	server_addr.sin_addr.s_addr = INADDR_ANY;//server ip (auto set by system)
	int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if(ret == -1)
	{
		perror("bind failed!\n");
		return -1;
	}

	//3 设置监听队列,设置为可以同时连接5个客户端
	ret = listen(socket_fd, 5);
	if(ret == -1)
	{
		perror("listen falied!\n");
		return -1;
	}


	printf("server is running!\n");

	struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
	int len = sizeof(client_addr);
	
	int i = 0;
	int socket_arr[5] = {0};//存放与客户端的通信socket
	pthread_t send_thread = 0;//存放发送线程的id
	while(1)//不断的接受客户端的请求
	{
		int new_socket_fd = -1;
		new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
		if(new_socket_fd == -1)
		{
			perror("accpet error!\n");
		}
		else
		{
			printf("[ID:%d] IP:%s, PORT:%d [connected]\n", i, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
			socket_arr[i] = new_socket_fd;

			//开启接收线程 一条接收线程对应一个连接的客户端
			pthread_t recv_thread;
			pthread_create(&recv_thread, NULL, recv_msg, (void*)&socket_arr[i]);
			i++;
			if(send_thread == 0)//只在第一次创建发送线程
			{
				pthread_create(&send_thread, NULL, send_msg, (void*)socket_arr);
			}
			
		}
	}

	//5 关闭socket
	close(socket_fd);

	return 0;
}

//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
	int *socket_fd = (int *)arg;//通信的socket
	while(1)
	{
		char buf[1024] = {0};
		read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
		printf("receive msg:%s\n", buf);
		if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
		{
			//通知主线程。。。
			
			break;//退出
		}
	}
	return NULL;
}

//发送线程所要执行的函数 发送消息
void * send_msg(void *arg)
{
	int (*socket_arr_pt)[5] = (int (*) [5])arg;//数组指针
	
	while(1)
	{
		int id = 0;
		char buf[1024] = {0};
		scanf("%d %s", &id, buf);
		if(id<0 || id>4)
		{
			printf("id 0~4");
			continue;
		}
		
		
		write( (*socket_arr_pt)[id], buf, sizeof(buf));
		
		if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
		{
			
			break;
		}
	}
	
	//close(socket_arr[0]);
	return NULL;
}

clinet.c文件 

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>

void * recv_msg(void *arg);

int main(int argc, char *argv[])
{
	//判断参数个数是否匹配
	if(argc != 3)
	{
		printf("请传递要连接的服务器的IP和端口号\n");
		return -1;
	}
	
	int port = atoi(argv[2]);//从命令行接收参数
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}

	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
	}

	//2 连接服务器
	struct sockaddr_in server_addr = {0};//服务器的地址信息
	server_addr.sin_family = AF_INET;//IPv4协议
	server_addr.sin_port = htons(port);//服务器端口号
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
	int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect failed!\n");
	}
	else
	{
		printf("connect server successful!\n");
	}

	
	//开启接收线程
	pthread_t recv_thread;//存放线程id       recv_msg:线程执行的函数,将通信socket:new_socket_fd传递进去
	ret = pthread_create(&recv_thread, NULL, recv_msg, (void*)&socket_fd);
	if(ret != 0)
	{
		printf("开启线程失败\n");
	}
	

	//3 循环发送消息
	while(1)
	{
		char buf[1024] = {0};
		scanf("%s",buf);
		write(socket_fd,buf,strlen(buf));
		if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
		{
			pthread_cancel(recv_thread);//取消接收线程
			break;
		}
	}

	//4 关闭socket
	close(socket_fd);

	return 0;
}

//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
	int *socket_fd = (int *)arg;//通信的socket
	while(1)
	{
		char buf[1024] = {0};
		read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
		printf("receive msg:%s\n", buf);
		if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
		{
			//通知主线程。。。
			
			break;//退出
		}
	}
	return NULL;
}

 

 例子6:使用多路复用实现  服务器与多个客户端的双向通信。

效果:

 

server.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()

int get_max_socket_fd(int socket_arr[]);//获取最大的通信socket描述符。

int main(int argc, char *argv[])
{
	//判断命令行参数是否满足
	if(argc != 2)
	{
		printf("请传递一个端口号\n");
		return -1;
	}

	//将接收端口号并转换为int
	int port = atoi(argv[1]);
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}

	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("创建tcp通信socket失败!\n");
		return -1;
	}

	//2 绑定socket地址
	struct sockaddr_in server_addr = {0};//存放地址信息
	server_addr.sin_family = AF_INET;//AF_INET->IPv4  
	server_addr.sin_port = htons(port);//端口号
	server_addr.sin_addr.s_addr = INADDR_ANY;//让系统检测本地网卡,自动绑定本地IP
	int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
	if(ret == -1)
	{
		perror("bind failed!\n");
		return -1;
	}

	//3 设置监听队列,设置为可以接受5个客户端连接
	ret = listen(socket_fd, 5);
	if(ret == -1)
	{
		perror("listen falied!\n");
	}


	printf("server is running!\n");

	
	struct sockaddr_in client_addr = {0};//用来保存客户端的地址信息
	int len = sizeof(client_addr);
	int socket_fd_arr[5] = {-1, -1, -1, -1, -1};//用来保存5个客户端的通信socket
	

	//定义一个文件描述符集合
	fd_set fds;
	//定义一个时间结构体  
	struct timeval  time;
	time.tv_sec = 3;//超时时间
	time.tv_usec = 0;
	//循环监视文件描述符集合 
	int new_socket_fd = -1;
	int max_socket_fd = -1;//保存最大的socket描述符
	int index = -1, i;
	
	//循环监视文件描述符集合
	while(1)
	{
		//清空文件描述符集合
		FD_ZERO(&fds);
		//把标准输入设备加入到集合中 
		FD_SET(0, &fds);
		FD_SET(socket_fd, &fds);//将监听socket添加到集合中
		
		//把网络通信文件描述符加入到集合中 
		for(i=0; i<=index; i++)
		{
			FD_SET(socket_fd_arr[i],&fds);
		}
		
		//获取最大的通信socket描述符。
		max_socket_fd = get_max_socket_fd(socket_fd_arr);
		if(max_socket_fd == -1)
		{
			max_socket_fd = socket_fd;
		}
		
		ret = select(max_socket_fd+1,&fds,NULL,NULL,&time);//检查集合中是否有活跃的描述符
		if(ret < 0)//错误
		{
			perror("select fail");
			return -1;
		}
		else if(ret == 0) //超时
		{
			//printf("timeout1\n");
		}
		else if(ret > 0) //有活跃的 
		{
			if(FD_ISSET(socket_fd,&fds))//监听socket活跃,说明有客户端请求连接
			{
				new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
				if(new_socket_fd == -1)
				{
					perror("accpet error!\n");
					continue;
				}
				else
				{
					if(index>=5)
					{
						index = 4;
						printf("index>=5\n");
						continue;
					}
					socket_fd_arr[++index] = new_socket_fd;//将通信socket保存到数组中
					printf("[ID:%d] IP:%s, PORT:%d [connected]\n", index, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
					
				}
			}
			
			//判断是否 标准输入设备活跃 假设是则发送数据 
			if(FD_ISSET(0,&fds))
			{
				char buf[1024] = {0};
				int client = -1;
				scanf("%d %s", &client, buf);
				if(client>=0 && client<=index)
				{
					write(socket_fd_arr[client],buf,strlen(buf));
					
				}
				else //给所有的客户端发消息
				{
					for(i=0; i<=index; i++)
					{
						write(socket_fd_arr[i],buf,strlen(buf));
					}
			
				}
				if(strcmp(buf, "exit") == 0)
				{
					break;
				}
			}
			
			//判断是否有收到消息 
			for(i=0; i<=index; i++)
			{
				if(FD_ISSET(socket_fd_arr[i],&fds))//判断通信socket是否有活跃
				{
					char buf[1024] = {0};
					read(socket_fd_arr[i], buf, sizeof(buf));
					
					if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
					{
						break;
					}
					else if(strlen(buf)>0)
					{
						printf("receive msg form [ID:%d]:%s\n", i, buf);
					}
				}
			}
		}
		
	}

	//5 关闭通信socket
	close(new_socket_fd);
	close(socket_fd);

	return 0;
}

//获取最大的通信socket描述符。
int get_max_socket_fd(int socket_arr[])
{
	int max = -1;
	int i;
	for(i=0; i<5; i++)
	{
		if(socket_arr[i]>max)
		{
			max = socket_arr[i];
		}
	}
	
	return max;
}

client.c文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()

int main(int argc, char *argv[])
{
	//检查命令行参数是否匹配
	if(argc != 3)
	{
		printf("请传递要连接的服务器的ip和端口号");
		return -1;
	}
	
	int port = atoi(argv[2]);//从命令行获取端口号
	if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
	{
		printf("端口号范围应为1025~65535");
		return -1;
	}
	
	//1 创建tcp通信socket
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd == -1)
	{
		perror("socket failed!\n");
	}

	//2 连接服务器
	struct sockaddr_in server_addr = {0};//服务器的地址信息
	server_addr.sin_family = AF_INET;//IPv4协议
	server_addr.sin_port = htons(port);//服务器端口号
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
	int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect failed!\n");
	}
	else
	{
		printf("connect server successful!\n");
	}

	//要监视的描述符集合
	fd_set fds;
	
	//3 send msg
	while(1)
	{
		//清空文件描述符集合
		FD_ZERO(&fds);
		//把标准输入设备加入到集合中 
		FD_SET(0,&fds);
		//把网络通信文件描述符加入到集合中 
		FD_SET(socket_fd,&fds);
		
		ret = select(socket_fd+1,&fds,NULL,NULL,NULL);
		if(ret < 0)//错误
		{
			perror("select fail:");
			return -1;
		}
		else if(ret > 0) //有活跃的
		{
			//判断是否 标准输入设备活跃 假设是则发送数据
			if(FD_ISSET(0,&fds))
			{
				char buf[1024] = {0};
				scanf("%s",buf);
				write(socket_fd,buf,strlen(buf));
				if(strcmp(buf, "exit") == 0)
				{
					break;
				}
			}

			//判断是否有收到数据
			if(FD_ISSET(socket_fd,&fds))
			{
				char buf[1024]={0};
				read(socket_fd,buf,sizeof(buf));
				printf("receive msg:%s\n",buf);
				if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
				{
					break;
				}


			}


		}
	
	}

	//4 关闭通信socket
	close(socket_fd);

	return 0;
}

----------------------------------------------------多路复用-----------------------------

再某些情况下代替多线程,以便节省资源。

select)函数
功能监视多个文件描述符,阻塞等待设定的时间,直到一个或多个文件描述符变为“准备好”,继续往下执行。
头文件#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
原型int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数nfds要监视的描述符集合中数值最大的描述符
readfds读文件描述符集合,通常设置为要监视的描述符集合
writefds写文件描述符集合,通常设置为NULL
exceptfds其它文件描述符集合,通常设置为NULL
 timeout

超时时间(阻塞等待的时间,时间一到,就会往下执行。如果为NULL,会一直等到有描述符活跃为止)

时间结构体 (定义在<sys/time.h> 头文件中)

struct timeval {
               long    tv_sec;         /* 秒*/
               long    tv_usec;        /* 微秒*/
};

返回值

-1:出错

0:超时

1:有活跃的文件描述符

备注

详细请查看man手册:man 2 select

相关函数:

void FD_CLR(int fd, fd_set *set); //将某个描述符 从集合中清除出去
int  FD_ISSET(int fd, fd_set *set);//判断某个描述符是否 活跃
void FD_SET(int fd, fd_set *set);//往描述符监视集合 添加一个描述符
void FD_ZERO(fd_set *set);//清空描述符集合

注:select会更新 超时时间timeout,所以如果多次使用select需要重置timeout。

往下执行时,将修改每个文件描述符集以指示哪些文件描述符实际更改了状态。 因此,如果在循环中使用select(),则需要在每次调用之前对集合进行重新定义。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐