自学内容网 自学内容网

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。

(后续文章会仔细讲解)

    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

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.执行子进程内容

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函数
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函数
void SignHandle(int sig){
if(sig==SIGCHLD){
printf("client exit\n");
wait(NULL);
}
}

 原始代码

#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.创建子线程

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.执行子线程函数

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;
}

 原始代码

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


原文地址:https://blog.csdn.net/weixin_64737319/article/details/140265289

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!