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结构体的函数返回值类型

相关推荐
一条晒干的咸魚1 分钟前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
所待.3835 分钟前
JavaEE之线程初阶(上)
java·java-ee
Winston Wood8 分钟前
Java线程池详解
java·线程池·多线程·性能
手握风云-13 分钟前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
东华果汁哥17 分钟前
【linux 免密登录】快速设置kafka01、kafka02、kafka03 三台机器免密登录
linux·运维·服务器
喵叔哟32 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生38 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
mengao12341 小时前
centos 服务器 docker 使用代理
服务器·docker·centos
不是二师兄的八戒1 小时前
本地 PHP 和 Java 开发环境 Docker 化与配置开机自启
java·docker·php
C-cat.1 小时前
Linux|进程程序替换
linux·服务器·microsoft