多线程实现TCP并发服务器使用setsockopt设置超时检测-使用setsockopt设置端口复用-传输层

使用setsockopt设置&getsockopt获取网络属性

在这里插入图片描述

网络超时检测概念

详情见:使用select函数实现超时检测

setsockopt、getsockopt

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
					  
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

参数:
	sockfd:套接字
	
	level:选项的级别
		SOL_SOCKET	套接字级别!!!!!!!!!!!
		IPPROTO_IP	IP级别
		SOL_PACKET	原始套接字
	
	optname:选项的名称
		不同的级别有不同的选项
		对于套接字API级别,常见的选项
			SO_BROADCAST	是否允许发送广播
			SO_RCVBUF		接收缓冲区大小(bytes)
			SO_SNDBUF		发送缓冲区大小(bytes)
			SO_REUSEADDR	端口复用
			SO_RCVTIMEO		接收超时时间 使用 timeval 结构体
			SO_SNDTIMEO		发送超时时间 使用 timeval 结构体
				如果超时,错误码会被设置成 EAGAIN   !!!!!!!!!!!!
				
	optval:选项的值
		没有特殊说明的情况下,该参数是一个 int * 指针
		接收超时时间 使用 timeval 结构体:
			struct timeval {
    			long    tv_sec;         /* 秒 */
    			long    tv_usec;        /* 微秒 */
			};
	
	optlen:
		optval的大小
		
返回值:
	成功  0
	失败  -1

使用多线程实现TCP并发服务器

无超时检测版:多线程实现TCP并发

recv — 在套接字中接收一条消息

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:
	sockfd:
		在哪个套接字中接收
	buf:
		存放要接收消息的缓冲区的首地址
	len:
		想要接受的数据的字节数
	flags:
		0				阻塞
		MSG_DONTWAIT	非阻塞
返回值:
	成功  实际读到的字节数
	失败  -1
	
	如果对方的socket已经关闭了,recv会返回0         !!!!!!!!!!!!

send — 向套接字中发送一条消息

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:
	sockfd:
		向哪个套接字发
	buf:
		要发送的数据的首地址
	len:
		想要发送的数据的字节数
	flags:
		0				阻塞
		MSG_DONTWAIT	非阻塞
返回值:
	成功  实际发送的字节数
	失败  -1

	如果对方的socket已经关闭了,第一次send会返回0
             				 第二次send会报错  SIGPIPE       !!!!!!!!!!!

代码实现

服务器—01server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
/*
#include <sys/time.h>*/
#include <errno.h>

#define ERRLOG(errmsg)                                       \
	do                                                       \
	{                                                        \
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
		perror(errmsg);                                      \
		exit(-1);                                            \
	} while (0)

//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
typedef struct
{
	int accept_fd;
	struct sockaddr_in client_addr;
} num_t;

//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);
//子线程处理
void *task1(void *arg);


int main(int argc, const char *argv[])
{
	//检测命令行参数个数
	if (3 != argc)
	{
		printf("Usage : %s <IP> <PORT>\n", argv[0]);
		exit(-1);
	}

	//创建套接字-填充服务器网络信息结构体-绑定-监听
	int sockfd = socket_bind_listen(argv);

	//用来保存客户端信息的结构体
	struct sockaddr_in client_addr;
	memset(&client_addr, 0, sizeof(client_addr));
	socklen_t client_addr_len = sizeof(client_addr);

	//传递信息的结构体
	num_t ku;
	memset(&ku, 0, sizeof(ku));//清空
	//创建线程
	pthread_t tid;

	while (1)
	{
		//由已经设置过超时时间的sockfd产生的acceptfd也会继承这个超时属性
		//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
		ku.accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
		if (-1 == ku.accept_fd)
		{
			if(errno == EAGAIN){
				//5秒没有新的客户端连接
				printf("accept 5秒超时...\n");
				continue;
			}
			else
			{
				ERRLOG("accept error");
			}
		}
		printf("客户端 (%s:%d) 连接了\n",
			   inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
		//创建子线程处理新客户端的读写请求
		//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
		ku.client_addr = client_addr;
		if (pthread_create(&tid, NULL, task1, (void *)&ku))
			ERRLOG("create tid2 error");
		//设置线程分离属性,无需手动回收资源
		//分离态的线程结束后资源会被自动回收,这个函数不会阻塞
		pthread_detach(tid);
	}
	//关闭监听套接字  一般不关闭
	close(sockfd);
	return 0;
}

//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[])
{
	// 1.创建套接字      //IPV4   //TCP
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
		ERRLOG("socket error");

	// 2.填充服务器网络信息结构体
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr)); //清空
	server_addr.sin_family = AF_INET;			  // IPV4

	//端口号  填 8888 9999 6789 ...都可以
	// atoi字符串转换成整型数
	//将无符号2字节整型  主机-->网络
	server_addr.sin_port = htons(atoi(argv[2]));

	// ip地址 要么是当前Ubuntu主机的IP地址 或者
	//如果本地测试的化  使用  127.0.0.1 也可以
	//字符串转换成32位的网络字节序二进制值
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//结构体长度
	socklen_t server_addr_len = sizeof(server_addr);
	/*----------------------------------------------------------------------------*/
 	//设置端口复用
    int on_off= 1;
    if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on_off, sizeof(on_off)))
        ERRLOG("setsockopt error");
	/*----------------------------------------------------------------------------*/
	//设置超时检测时间 5s
	struct timeval tm;
	tm.tv_sec = 5;   //秒
	tm.tv_usec = 0;  //微秒
	//设置套接字的网络属性        /套接字级别  /接收超时时间
	if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)))
		ERRLOG("setsockopt error");
	
	/*----------------------------------------------------------------------------*/
	// 3.将套接字和网络信息结构体绑定
	if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
		ERRLOG("bind error");

	//将套接字设置成被动监听状态
	if (-1 == listen(sockfd, 10))
		ERRLOG("listen error");

	return sockfd;
}

//子线程处理
void *task1(void *arg)
{
	//线程的处理函数
	num_t ku = *(num_t *)arg;
	char buff[128] = {0}; //临时存放数据
	int ret = 0;
	//接收客户端发来的数据
	while (1)
	{
		//由已经设置过超时时间的sockfd产生的acceptfd也会继承这个超时属性
		//也就是说,客户端5秒没有给服务器发消息 也会报错 recv超时
		//如果想使用相同的时间,直接使用即可;如果不想使用这个时间,可以再次调用setsockopt重新设置
		if (0 > (ret = recv(ku.accept_fd, buff, sizeof(buff), 0)))
		{
			if (errno == EAGAIN)
			{
				//客户端5秒没有给服务器发数据
				printf("recv接收,超时...\n");
				//可以踢掉该客户端 break之后关闭套接字即可
				break;
			}

			ERRLOG("recv error");
		}
		else if (0 == ret) //客户端侧CTRL+C
		{
			printf("客户端 (%s:%d) 断开连接\n",
				   inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
			break;
		}
		else
		{
			if (0 == strcmp(buff, "quit"))
			{
				printf("客户端 (%s:%d) 退出了\n",
					   inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
				break;
			}
			printf("客户端 (%s:%d) 发来数据:[%s]\n",
				   inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port), buff);

			//组装回复给客户端的应答
			strcat(buff, "---996");
			//回复应答
			if (0 > (ret = send(ku.accept_fd, buff, sizeof(buff), 0)))
				ERRLOG("send error");
		}
	}
	//关闭该客户端的套接字
	close(ku.accept_fd);
	//线程退出
	pthread_exit(NULL);
}

客户端—02client.c

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

#define ERRLOG(errmsg)                                       \
	do                                                       \
	{                                                        \
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
		perror(errmsg);                                      \
		exit(-1);                                            \
	} while (0)

int main(int argc, const char *argv[])
{
	//检测命令行参数个数
	if (3 != argc)
	{
		printf("Usage : %s <IP> <PORT>\n", argv[0]);
		exit(-1);
	}

	// 1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
		ERRLOG("socket error");

	// 2.填充服务器网络信息结构体
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	//端口号  填 8888 9999 6789 ...都可以
	server_addr.sin_port = htons(atoi(argv[2]));
	// ip地址 要么是当前Ubuntu主机的IP地址 或者
	//如果本地测试的化  使用  127.0.0.1 也可以
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//结构体长度
	socklen_t server_addr_len = sizeof(server_addr);

	//与服务器建立连接
	if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
		ERRLOG("connect error");

	printf("---连接服务器成功---\n");
	char buff[128] = {0};
	while (1)
	{
		scanf("%s", buff);
		int ret = 0;
		if (0 > (ret = send(sockfd, buff, sizeof(buff), 0)))
			ERRLOG("send error");

		if (0 == strcmp(buff, "quit"))
			break;

		if (0 > (ret = recv(sockfd, buff, sizeof(buff), 0)))
		{
			ERRLOG("recv error");
		}
		else if(0==ret)
		{
			//说明超时了 服务器已经把客户端踢掉了
			//客户端5秒没有给服务器发数据
			//服务器break之后关闭套接字
			printf("由于你长时间没有说话,服务器已经把你踢掉了...\n");
			break;
		}

		printf("收到服务器回复:[%s]\n", buff);
	}
	//关闭套接字
	close(sockfd);

	return 0;
}

执行结果

5秒不连接、不发数据,服务器端现象

在这里插入图片描述

5秒不发数据,客户端现象

在这里插入图片描述

注意

使用setsockopt设置套接字的网络属性

应该插放socket创建套接字 - bind将套接字和网络信息结构体绑定 之间

	1.创建套接字
	socket()
	2.填充服务器网络信息结构体
	struct sockaddr_in
----------------------------------------------------------
	3.设置超时检测时间 5s
	struct timeval tm;
	tm.tv_sec = 5;   //秒
	tm.tv_usec = 0;  //微秒
	4.设置套接字的网络属性         /套接字级别  /接收超时时间
	if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)))
		ERRLOG("setsockopt error");
----------------------------------------------------------
	5.将套接字和网络信息结构体绑定
	bind()

使用setsockopt设置端口复用

应该插放socket创建套接字 - bind将套接字和网络信息结构体绑定 之间

	1.创建套接字
	socket()
	2.填充服务器网络信息结构体
	struct sockaddr_in
----------------------------------------------------------
	3.设置超时检测时间 5s
	int on_off= 1;
	4.设置端口复用               /套接字级别    /端口复用
	if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on_off, sizeof(on_off)))
		ERRLOG("setsockopt error");
----------------------------------------------------------	
	5.将套接字和网络信息结构体绑定
	bind()

6.非原创

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值