tcp并发设计

4注意:原始代码,如果先关闭服务器端,再次开启服务器的时候会报"connect: Connection refused

"错误,这是因为先关服务器端,导致系统认为客户端仍然在与服务器端连接造成。

可以使用setsockopt

setsockopt函数用于设置套接字选项,可以用来控制套接字的行为和属性。通过setsockopt函数,可以设置套接字的各种选项,如超时设置、缓冲区大小、复用地址、广播等。这些选项可以影响套接字的连接、通信、数据传输等方面的行为,使程序更加灵活、高效地运行。通过设置不同的选项,可以满足不同的网络编程需求。

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

参数说明:

  • `sockfd`为目标套接字的文件描述符。

  • `level`为选项所在的协议层,一般为`SOL_SOCKET`表示Socket级别选项。

  • `optname`为要设置的选项名。

  • `optval`为指向包含新选项值的缓冲区的指针。

  • `optlen`为新选项值的长度。

成功时返回0,失败时返回-1并设置errno。

(后续文章会仔细讲解)

cpp 复制代码
    int flag=1,len=sizeof(int);
    if(setsockopt(fd,SOL_SOCKET,SO_REUSEADD,&flag,len)==-1){
        perror("setsockopt");
        exit(0);
    }

进程并发设计

并法思路:

一个新生成的文件描述符对应一个客户端,如果多个客户端就需要多个accept函数生成多个文件描述符

注意:给客户端的端口号代表的是服务器的端口号,为了确定是连接的是哪个服务器

accept(fd, (struct sockaddr *)&clint_addr, &addrlen);accept获取的是客户端的端口

1.创建子进程

子进程接收accept返回的新的newfd

cpp 复制代码
while(1){
	newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
	if(newfd < 0){
		perror("accept");
		exit(0);
	}
		printf("addr:%s port:%d\n",inet_ntoa(clint_addr.sin_addr),ntohs(clint_addr.sin_port));
	if((pid=fork())<0){
		perror("fork");
		exit(0);
	}else if(pid==0){
		close(fd);//此时子进程不用fd,将子进程中fd关掉
		ClientHandle(newfd);//此时子进程打开了一个newfd,父进程也打开了一个newfd,浪费资源,因此将父进程的newfd关掉
		exit(0);
	}else
		close(newfd);//父进程不用newfd,将父进程中newfd关掉
	}
	close(fd);
	return 0;
}
void ClientHandle(int newfd){
	int ret;
	char buf[BUFSIZ]={};

2.执行子进程内容

cpp 复制代码
void ClientHandle(int newfd){
	int ret;
	char buf[BUFSIZ]={};
	while(1){
	//	memset(buf, 0, BUFSIZ);
		bzero(buf,BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
}

3.回收子进程

通过信号机制
1.初始化signaction函数
cpp 复制代码
	struct sigaction act;
	act.sa_handler=SignHandle;
	act.sa_flags=SA_RESTART;
	sigemptyset(&act.sa_mask);
	sigaction(SIGCHLD,&act,NULL);

注意:

accept: Interrupted system call

当回收进程时,可能会出现"accept: Interrupted system call"错误,是因为使用信号机制时导致某些系统调用被终止了,此时设置sigaction函数结构体中的sa_flags=SA_RESTART,让意外终止的系统调用继续运行。

(可以通过man signaction命令查看)

2.创建SignHandle函数
cpp 复制代码
void SignHandle(int sig){
	if(sig==SIGCHLD){
		printf("client exit\n");
		wait(NULL);
	}
}

原始代码

cpp 复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include<strings.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
#define BACKLOG 5
void ClientHandle(int newfd);
void SignHandle(int sig);
void SignHandle(int sig){
	if(sig==SIGCHLD){
		printf("client exit\n");
		wait(NULL);
	}
}
int main(int argc, char *argv[])
{
	int fd, newfd;
	struct sockaddr_in addr,clint_addr;
	socklen_t addrlen;//尽量不使用指针类型,如果指针指向的地址没有存储给定的数据,很可能成为野指针
	addrlen=sizeof(clint_addr);
	pid_t pid;
	struct sigaction act;
	act.sa_handler=SignHandle;
	act.sa_flags=SA_RESTART;
	sigemptyset(&act.sa_mask);
	sigaction(SIGCHLD,&act,NULL);
	
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	int flag=1,len=sizeof(int);
	if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&flag,len)==-1){
		perror("setsockopt");
		exit(0);
	}
	if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("bind");
		exit(0);
	}

	if(listen(fd, BACKLOG) == -1){
		perror("listen");
		exit(0);
	}
	//此新的文件描述符用于跟客户通讯
	//并法思路:一个新生成的文件描述符对应一个客户端,如果多个客户端就需要多个accept函数生成多个文件描述符
	while(1){
	newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
	if(newfd < 0){
		perror("accept");
		exit(0);
	}
		printf("addr:%s port:%d\n",inet_ntoa(clint_addr.sin_addr),ntohs(clint_addr.sin_port));
	if((pid=fork())<0){
		perror("fork");
		exit(0);
	}else if(pid==0){
		close(fd);//此时子进程不用fd,将子进程中fd关掉
		ClientHandle(newfd);//此时子进程打开了一个newfd,父进程也打开了一个newfd,浪费资源,因此将父进程的newfd关掉
		exit(0);
	}else
		close(newfd);//父进程不用newfd,将父进程中newfd关掉
	}
	close(fd);
	return 0;
}
void ClientHandle(int newfd){
	int ret;
	char buf[BUFSIZ]={};
	while(1){
	//	memset(buf, 0, BUFSIZ);
		bzero(buf,BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
}

线程并发设计

并发思路:通过accept返回的新的文件描述符,传参给子线程

1.创建子线程

cpp 复制代码
	while(1){
		
		newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
		if(newfd < 0){
			perror("accept");
			exit(0);
		}
		printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
		pthread_create(&tid, NULL, ClinetHandle, &newfd);
		pthread_detach(tid);//设置为分离模式,不需要回收线程
	}

2.执行子线程函数

cpp 复制代码
void *ClinetHandle(void *arg){
	int ret;
	char buf[BUFSIZ] = {};
	int newfd = *(int *)arg;
	while(1){
		//memset(buf, 0, BUFSIZ);
		bzero(buf, BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
	printf("client exited\n");
	close(newfd);
	return NULL;
}

原始代码

cpp 复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>

#define BACKLOG 5

void *ClinetHandle(void *arg);
int main(int argc, char *argv[])
{
	int fd, newfd;
	struct sockaddr_in addr, clint_addr;
	pthread_t tid;
	socklen_t addrlen = sizeof(clint_addr);
	
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}


	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}


	int flag=1,len= sizeof (int); 
	if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { 
		      perror("setsockopt"); 
			        exit(1); 
	} 

	if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("bind");
		exit(0);
	}

	if(listen(fd, BACKLOG) == -1){
		perror("listen");
		exit(0);
	}
	while(1){
		
		newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
		if(newfd < 0){
			perror("accept");
			exit(0);
		}
		printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
		pthread_create(&tid, NULL, ClinetHandle, &newfd);
		pthread_detach(tid);//设置为分离模式,不需要回收线程
	}
	close(fd);
	return 0;
}
void *ClinetHandle(void *arg){
	int ret;
	char buf[BUFSIZ] = {};
	int newfd = *(int *)arg;
	while(1){
		//memset(buf, 0, BUFSIZ);
		bzero(buf, BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
	printf("client exited\n");
	close(newfd);
	return NULL;
}

注意:

vi排版:按v,上下键选中需要排版的代码,按=即可

相关函数:

`bzero()` 函数用于将指定长度的内存区域清零,即将所有字节初始化为0。它通常用于清空敏感的数据或准备数据结构。`bzero()` 函数在许多系统中已经被废弃,应该使用更现代的函数`memset()` 来替代。其原型如下:

include<strings.h>

void bzero(void *s, size_t n);

其中,参数 `s` 是指向要清零的内存区域的指针,参数 `n` 是要清零的字节数。

man inet_addr可以查看使用in_addr结构体的函数返回值类型

相关推荐
逊嘘17 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
morris13124 分钟前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
龙哥说跨境40 分钟前
如何利用指纹浏览器爬虫绕过Cloudflare的防护?
服务器·网络·python·网络爬虫
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员1 小时前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU1 小时前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie61 小时前
在IDEA中使用Git
java·git
懒大王就是我1 小时前
C语言网络编程 -- TCP/iP协议
c语言·网络·tcp/ip
Elaine2023911 小时前
06 网络编程基础
java·网络
G丶AEOM1 小时前
分布式——BASE理论
java·分布式·八股