linux网络编程自定义协议和多进程多线程并发-TCP编程

1.三次握手及后面过程

计算机A是客户端,  B是服务端

1.1三次握手:

        1客户端给服务端SYN报文

        2服务端返回SYN+ACK报文

        3客户端返回ACK报文

        客户端发完ACK后加入到服务端的维护队列中,accept()调用后就能和客户端建立连接,然后建立通讯

1.2关闭连接过程

        客户端发送FIN报文给服务端,就是用来关闭连接的请求

        服务器端返回ACK报文

        服务端发送FIN报文

        服务端返回ACK报文

2.自定义协议

2.1TCP模型 ------作为自定义协议参考

2.2,自定义协议的Msg结构体加上了头部,校验码和体部

1.Msg.h如下

#ifndef __MSG_H__
#define __MSG_H__#include <sys/types.h>typedef struct{//协议头部char head[10]; //头部char checknum;  //校验码,验证发送方和接收方是否一样//协议体部char buff[512];  //数据
}Msg;/**发送一个基于自定义协议的message*发送的数据存放在buff中*/
extern int write_msg(int sockfd, char *buff, size_t len);/**读取一个基于自定义协议的message*读取的数据存放在buff中*/
extern int read_msg(int sockfd, char *buff,size_t len);#endif

2.Msg.c如下

#include "msg.h"
#include <unistd.h>
#include <string.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>//计算校验码,加的是每个字符的ASCII码值
//unsigned char s是0~255范围,溢出了会回绕
//通过这样检验消息头部和体部加每个字符ASCII码,溢出回绕
//如果传输无差错最终得到一个定值
static unsigned char msg_check(Msg *message)
{unsigned char s = 0;int i;//遍历head,和buff数组for(i = 0; i < sizeof(message->head); i++){s += message->head[i];}for(i = 0; i < sizeof(message->buff); i++){s += message->buff[i];}return s;
} /**发送一个基于自定义协议的message*发送的数据存放在buff中*/
int write_msg(int sockfd, char *buff, size_t len)
{Msg message;//定义发送的信息memset(&message, 0, sizeof(message));//头部strcpy(message.head, "jwp202411");//体部,用于内存拷贝,len指定字节memcpy(message.buff, buff, len);//校验码message.checknum = msg_check(&message);//通过文件IO write写入管道if(write(sockfd, &message,sizeof(message)) != sizeof(message)){return -1;  //写入出错}return 0;//写入成功
}/**是结构体message的*读取一个基于自定义协议的message*读取的体部数据存放在buff中*/
int read_msg(int sockfd, char *buff,size_t len)
{Msg message;memset(&message, 0, sizeof(message));size_t size;//读socket套接字的描述符,发送方也是和//接收方发送的统一message结构体格式接收方,这里是这样的if(size = read(sockfd, &message, sizeof(message) < 0)){perror("message read error");return -1;}else if(size == 0){//写的一端关闭读的一端会读到0个return 0;}//进行校验码验证//验证接收到的发送方校验和读取message计算的//是否相同//为了验证发送是否出现少发或者发错unsigned char s = msg_check(&message);if((s == (unsigned char)message.checknum) && (!strcmp("jwp202411", message.head))){//验证接收到的数据没问题就拷贝接收到的字节到buff中memcpy(buff, message.buff, len);//返回接收到的字节数return sizeof(message);}  return -1;
}

2.3memcpy()用于拷贝内存,strcpy()用于拷贝字节

memcpy() 和 strcpy() 是 C 语言中用于内存操作的两个标准库函数,它们各自有不同的用途和特点。memcpy()
memcpy() 函数用于复制内存区域。它从源内存区域复制指定数量的字节到目标内存区域。函数原型如下:c
深色版本
void *memcpy(void *dest, const void *src, size_t n);
dest:指向目标对象的指针,即数据将要被复制到的地方。
src:指向源对象的指针,即数据将要从这里被复制。
n:要复制的字节数。
这个函数不会检查源字符串是否以空字符 \0 结束,也不会添加终止符。因此,它适用于复制任何类型的内存块,包括非字符数组。strcpy()
strcpy() 函数专门用于复制字符串。它从源字符串复制字符(包括终止空字符 \0)到目标字符串。函数原型如下:c
深色版本
char *strcpy(char *dest, const char *src);
dest:指向目标字符串的指针,该字符串应当有足够的空间来容纳源字符串的所有字符加上终止符。
src:指向源字符串的指针,该字符串必须是以空字符 \0 结尾的。
strcpy() 会自动在复制的目标字符串末尾添加一个空字符 \0,确保目标字符串也是以空字符结尾的。使用注意事项
缓冲区溢出:使用 strcpy() 时需要特别注意目标缓冲区是否有足够的空间来存放源字符串以及终止符,否则可能会导致缓冲区溢出,这是一个常见的安全漏洞。
类型安全:memcpy() 可以用于复制任何类型的数据,而不仅仅是字符数组。但是,当复制结构体等复杂数据类型时,需要确保目标和源的数据类型兼容,并且复制的字节数正确。
性能差异:对于简单的字符串复制任务,strcpy() 可能更方便,因为它自动处理了字符串终止符。但对于已知大小的内存块复制,memcpy() 通常更加高效。
总之,选择使用哪个函数取决于具体的应用场景和需求。如果只是简单地复制字符串,strcpy() 是一个合适的选择;而对于更复杂的内存操作或需要精确控制复制字节数的情况,则应该使用 memcpy()。

2.4编译命令

gcc -o obj/msg.o -Iinclude -c src/msg.c

提供的命令 gcc -o obj/msg.o -Iinclude -c src/msg.c 是用于编译 C 源文件 src/msg.c 并生成目标文件 obj/msg.o

1,gcc:
这是 GNU 编译器集合(GCC)的命令行工具,用于编译 C、C++ 等语言的源代码。
-o obj/msg.o:
-o 选项用于指定输出文件的名称。
2,obj/msg.o 是输出文件的路径和名称。在这个例子中,编译器会生成一个名为 msg.o 的目标文件,并将其放在 obj 目录中。
3,-Iinclude:
-I 选项用于指定额外的头文件搜索路径。
include 是目录名,表示编译器会在 include 目录中查找头文件。这对于包含自定义头文件非常有用。
-c:
-c 选项告诉编译器只进行编译和汇编步骤,生成目标文件(.o 文件),但不进行链接。
这意味着编译器不会生成最终的可执行文件,而是生成一个中间目标文件。
4,src/msg.c:
这是要编译的源文件的路径和名称。
src/msg.c 表示源文件位于 src 目录中,文件名为 msg.c。

编译结果

3.文件IO,管道IO,网络编程的IO

1.当服务端调用read(),但没有接收到数据,会阻塞等待信息来临,除非终止等待的客户端进程

当服务端调用read(sockfd ....);但客户端没有发送消息的方式过来服务器端会阻塞,这样再有其他客户端请求服务端连接只被Listen()到了监听队列中,但服务器端被前一个连接上了的客户端阻塞,服务端就无法调用accept()从队列中与客户端连接

所以要并发处理客户端请求,通过服务端一个进程,由此引出服务端并发性处理

1.多进程模型

服务端进程看作父进程,第一个客户端过来创建子进程负责和客户端通讯,第二个客户端过来创建一个子进程也是负责和这个客户端通讯,父进程就光accept()负责和客户端进行连接

2.客户端与服务端的连接类似管道,

3.do_service()服务器端成功读取把读取到的信息写回去通过wirte_msg()

不完整管道:当读端关闭,往写端写入数据,会产生SIGPIPE信号,同时错误编号设置errno = EPIPE,  客户端正好关闭了,服务端正好写入的时候,

2.多线程模型

3.I/O多路转换

4.多进程并发处理及代码

1.echo_tcp_server.c如下

#include <stdio.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include "msg.h"
#include <errno.h>int sockfd;  //服务器端套接字描述符//处理ctrl+c终止服务器函数
void sig_handler(int signo)
{if(signo == SIGINT){printf("server close\n");/*步骤6,关闭sockt*/close(sockfd);exit(0);}if(signo == SIGCHLD){//子进程结束父进程回收它printf("SIGCHLD child processing dead..\n");//等待任意一个子进程终止,并回收子进程占用资源wait(0);}
}//输出连接上来的客户端的相关信息
void out_addr(struct sockaddr_in *client)
{//获得端口,是个网络字节序(转成主机字节序)int port = ntohs(client->sin_port);char ip[16];//3*4+3个点+一个结束字符=16memset(ip, 0, sizeof(ip));//将IP地址从网络字节序转换成点分十进制inet_ntop(AF_INET, &client->sin_addr.s_addr, ip, sizeof(ip));printf("client: %s(%d) connected\n", ip, port);
}void do_service(int fd)
{/*和客户端进行读写操作(双向通信)*/char buff[512];while(1){memset(buff, 0, sizeof(buff));//清零printf("start read and write...\n");ssize_t size;//根据协议读取到buffif((size = read_msg(fd, buff, sizeof(buff))) < 0){//协议错误perror("protocol error");break;} else if(size == 0) {printf("pipe no connect\n");//返回0则表示read到的为0,写端没有连接或者发送,退出do_servicebreak;//结束do_service} else {//服务器端把接收到的数据输出printf("%s\n", buff);//发送读到的msg给客户端if(write_msg(fd, buff, size) < 0){//不完整管道会设置errno = EPIPE,也会产生SIGPIPE信号//也可以捕获SIGPIPE信号if(errno == EPIPE){printf("pipe incomplete");//对方已经关闭了读管道,则管道未连接break;}perror("write_msg protocol error");}printf("write success back to client\n");}}
}int main(int argc, char *argv[])
{//服务器端指定端口,要监听,命令行中传递进去if(argc < 2){printf("usage: %s #port\n", argv[0]);exit(1);}//登记一个信号SIGINT,ctrl+c终止服务器端if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal sigint error");exit(1);}//子进程终止的信号if(signal(SIGCHLD, sig_handler) == SIG_ERR){perror("signal sigchld error");exit(1);}/*步骤1,创建socket(套接字),socket创建在内核中,是一个结构体,AF_INET:IPV4SOCK_STREAM:tcp协议  最后参数0*/int sockfd = socket(AF_INET, SOCK_STREAM, 0);/*步骤2:调用bind将socket(包括ip,port)进行绑定*///sockaddr_in,因特网的专用地址,sockaddr是通用地址struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));//清空//往地址中填入ip,port,internet地址族类型serveraddr.sin_family = AF_INET;//端口号需要为16位网络字节序,atoi()将字符串转换为intserveraddr.sin_port = htons(atoi(argv[1]));//网络字节序IP地址,INADDR_ANY响应所有网卡的请求//一台主机有多个网卡,多个IP地址,响应所有网卡来源上的客户端请求*/serveraddr.sin_addr.s_addr = INADDR_ANY;//绑定,第二个参数需要强转为struct sockaddr*if(bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0){perror("bind error");exit(1);}/*步骤3,调用listen函数启动监听(指定port端口监听)通知系统去接受来自客户端的连接请求,将接收到的客户端连接放在队列中backlog,指定客户端排队队列长度*/if(listen(sockfd, 10) < 0){perror("listen error");exit(1);}/*步骤4,获得某一个客户端的连接/调用accept函数从队列中获得一个 客户端的请求连接,并返回新的socket描述符, 在内部会新创建socket返回描述符,通过此描述符和客户端描述符进行通信,若没有客户端连接,调用accept()会阻塞,直到获得一个客户端连接并返回新的socket描述符第二个参数用于接收客户端地址信息*/struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);while(1){int fd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);if(fd < 0){perror("accept error");continue;//结束本次循环,继续下一次}/*步骤5,启动子进程调用IO函数(read/write)和连接的客户端进行双向的通讯*///父进程由accept()产生的fd会复制一份给子进程pid_t pid = fork(); if(pid < 0){//创建出错continue;} else if(pid == 0){ //子进程终止会产生SIG_CHILD信号//子进程不会继承父进程的函数调用栈。也就是说,//子进程不会继承父进程当前正在执行的函数调用状态//输出接收到的客户端地址out_addr(&clientaddr);//do_service()和客户端进行通讯服务,用于和客户端连接do_service(fd); //子进程继承fd//步骤6,关闭针对客户端的socket,close(fd); printf("Child process exiting after handling client request.\n");exit(0);  // 子进程处理完后退出} else { //父进程close(fd); //fd被子进程创建两次}}return 0;
}

值得注意的是,服务端fork()子线程处理服务端与客户端通讯,子进程要复制父进程的代码段,包括while()循环,所以子进程break的是子进程的while()循环,

子进程获得父进程数据空间,堆和栈的副本,子进程继承父进程所有的文件描述符,但不继承accept()函数和fork()函数,继承了父进程的while(1)循环,所以父进程处理accept()和客户端队列的连接,子进程处理do_service(), 子进程不会继承父进程的函数调用栈。也就是说,子进程不会继承父进程当前正在执行的函数调用状态,

2.echo_tcp_client.c如下

#include <netdb.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include "msg.h"//自定义协议客户端int sockfd;//处理ctrl+c终止客户端函数
void sig_handler(int signo)
{if(signo == SIGINT){printf("server close\n");/*步骤6,关闭sockt*/close(sockfd);exit(0);}
}int main(int argc, char *argv[])
{if(argc < 3){printf("usage: %s ip port\n", argv[0]);exit(1);}if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal sigint error");exit(1);}/*步骤1:创建socket*/sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket error");exit(1);}//因特网专用结构体//往sockaddr中填入ip.port和地址族类型ipv4struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET; //IPV4serveraddr.sin_port = htons(atoi(argv[2]));//将点分十进制的ip地址转换为网络字节序//第二个参数是输入的字符串形式ip地址,转换到第三个参数(网络字节序)//第三个参数是serceraddr.sin_addr.s_addr的地址int res = inet_pton(AF_INET, argv[1],&serveraddr.sin_addr.s_addr);if(res < 0){perror("inet_pton error");exit(1);}/*步骤2:客户端调用connect函数连接到服务器端*/if(connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0){printf("connect error");exit(1);}/*步骤3:调用IO函数(read/write)和服务器端进行双向通信*/char buff[512];size_t size;char *prompt = ">";  //提示while(1){memset(buff, 0, sizeof(buff));//写提示到标准输出write(STDOUT_FILENO, prompt, 1);//获取标准输入要发送的信息size = read(STDIN_FILENO, buff, sizeof(buff) - 1);  //减1防止溢出if(size < 0) continue;  //结束本次循环,继续下一次//read函数读取存到buff里的字符不会加'\0'buff[size - 1] = '\0';//通过客户端创建的套接字描述符//自定义的write函数发送给服务端//发送实际读取的字节数if(write_msg(sockfd, buff, size - 1) == -1){//发送错误perror("write msg error");continue;}else{//发送成功,读取服务端返回的信息//printf("read from server\n");if(read_msg(sockfd, buff, sizeof(buff)) < 0){printf("read error\n");perror("read msg error");continue;}else{//读取服务器端返回信息成功//正常读取,把客户端读到的数据输出到标准输出//printf("read success\n");printf("%s\n", buff);  // \n让标准输出从缓存中刷新到终端}}}/*步骤4:关闭socket*/close(sockfd);return 0;
}

3.编译和运行命令如下

1.编译命令

gcc -o bin/echo_tcp_server -Iinclude obj/msg.o src/echo_tcp_server.cgcc -o bin/echo_tcp_client -Iinclude obj/msg.o src/echo_tcp_client.c 

2.运行命令

 ./bin/echo_tcp_server 8888./bin/echo_tcp_client 127.0.0.1 8888

3.客户端运行结果

4.服务端运行结果

5.多线程并发处理及代码

1.多线程比较多进程的好处:

多进程并发处理多客户端请求是有弊端的,多个客户端同时访问,服务端需要启动许多个子进程,每个子进程都要占用系统资源,导致服务端处理大并发速度变慢,最终导致服务端挂掉。

2.多线程服务器模型处理客户端并发处理

线程不太占用资源,服务器端可以针对连接的多个客户端,服务器端启动多个针对性的子线程,让各个子线程去针对性对应于某个客户端

所以可以基于多进程改进,accept()后将启动子进程改为启动子线程

有一个主控线程,通过主控线程去创建子线程,每个子线程都服务于某个客户端,所有子线程共享同一进程资源,子线程占有资源后要自动释放资源,比如栈空间的资源。

两种方式自动释放:

1:主控线程创建并启动子线程,主控线程调用pthread_join(),主控线程自己阻塞

子线程结束,它所占用资源被释放,当多个子线程都调用pthread_join(),会导致主控线程持续阻塞,导致无法处理新的客户端请求。accept(),主控线程不断循环做accept()负责服务端和客户端连接,所以调用pthread_join()释放占用资源是不合适的

2:以分离状态启动的子线程,当子线程运行结束,它占用的资源会自动释放,主控线程做循环调用accept();

综述:使用分离状态启动子线程,在主控线程循环调用accept()来负责服务器和客户端的连接,没有客户端连接到客户端时,while(1)循环先执行accept(),由于accept()在listen()监听队列没有找到已完成的连接请求(已完成的连接请求来自listen()的监听队列),就会阻塞while(1),一旦有另外的客户端请求连接,阻塞解除,以分离状态启动子线程,并传入accept()返回的socket描述符给线程运行函数,负责处理和客户端的通讯和显示客户端信息。

bind()负责绑定socket()创建的套接字到某个具体的IP地址(可以在初始网络因特网专用结构体把IP=INADDR_ANY就能响应各个IP的连接请求)和端口是自己从终端输入或者绑定的

3.处理并发的场景,不止分离状态启动子线程,在高级编程中,以IO多路转换

4还有非阻塞方式 fcntl

3.以分离状态启动子线程

pthread_tcp_server.c代码

多线程模型以分离状态处理客户端并发请求的场景

getpeername()  ,以socket文件描述符,获得accept()返回的socket()描述符fd,来获取客户端的地址信息,端口号(网络字节序),IP地址(网络字节序)要通过inet_ntop()网络字节序转换为点分十进制字节序,然后打印出来

//从fd中获得连接的客户端相关信息
    //存放在传入的addr第二个专用地址结构体中
    if(getpeername(fd, (struct sockaddr*)&addr, &len) < 0){
        perror("getpeername error");
        return;
    }
    char ip[16];
    memset(ip, 0, sizeof(ip));
    int port = ntohs(addr.sin_port); //主机端口
    //将网络字节序ip地址转换为点分十进制字符ip地址
    inet_ntop(AF_INET, 
    &addr.sin_addr.s_addr, ip, sizeof(ip));
    printf("%16s(%5d) closed!\n", ip, port);

#include <stdio.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include "msg.h"
#include <errno.h>
#include <pthread.h>int sockfd;  //服务器端套接字描述符//处理ctrl+c终止服务器函数
void sig_handler(int signo)
{if(signo == SIGINT){printf("server close\n");/*步骤6,关闭sockt*/close(sockfd);exit(0);}
}//负责与客户端进行通讯
void do_service(int fd)
{/*和客户端进行读写操作(双向通信)*/char buff[512];while(1){memset(buff, 0, sizeof(buff));//清零ssize_t size;//根据协议读取到buffif((size = read_msg(fd, buff, sizeof(buff))) < 0){//协议错误perror("protocol error");break;} else if(size == 0) {printf("client breaked connection\n");//返回0则表示read到的为0,写端没有连接或者发送,退出do_servicebreak;//结束do_service} else {//服务器端把接收到的数据输出printf("%s\n", buff);//发送读到的msg给客户端if(write_msg(fd, buff, size) < 0){//不完整管道会设置errno = EPIPE,也会产生SIGPIPE信号//也可以捕获SIGPIPE信号if(errno == EPIPE){printf("pipe incomplete");//对方已经关闭了读管道,则管道未连接break;}perror("write_msg protocol error");}}}
}//通过传入accept()返回的socket
//描述符获取客户端地址信息
void out_fd(int fd)
{//通过accept()返回的fd套接字描述符//获取客户端信息//专用sockaddr_in地址结构体struct sockaddr_in addr;socklen_t len = sizeof(addr);//从fd中获得连接的客户端相关信息//存放在传入的addr第二个专用地址结构体中if(getpeername(fd, (struct sockaddr*)&addr,&len) < 0){perror("getpeername error");return;}char ip[16];memset(ip, 0, sizeof(ip));int port = ntohs(addr.sin_port); //主机端口//将网络字节序ip地址转换为点分十进制字符ip地址inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip));printf("%16s(%5d) closed!\n", ip, port);}//线程运行函数
void* th_fn(void *arg)
{int fd = (int)arg;//开始读写printf("start read and write...\n");do_service(fd);//do_service结束要么是客户端挂掉了,要么数据通信完毕了//输出相关客户端信息out_fd(fd);close(fd);return (void*)0;
}int main(int argc, char *argv[])
{//服务器端指定端口,要监听,命令行中传递进去if(argc < 2){printf("usage: %s #port\n", argv[0]);exit(1);}//登记一个信号SIGINT,ctrl+c终止服务器端if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal sigint error");exit(1);}/*步骤1,创建socket(套接字),socket创建在内核中,是一个结构体,AF_INET:IPV4SOCK_STREAM:tcp协议  最后参数0*/int sockfd = socket(AF_INET, SOCK_STREAM, 0);/*步骤2:调用bind将socket(包括ip,port)进行绑定*///sockaddr_in,因特网的专用地址,sockaddr是通用地址struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));//清空//往地址中填入ip,port,internet地址族类型serveraddr.sin_family = AF_INET;//端口号需要为16位网络字节序,atoi()将字符串转换为intserveraddr.sin_port = htons(atoi(argv[1]));//网络字节序IP地址,INADDR_ANY响应所有网卡的请求//一台主机有多个网卡,多个IP地址,响应所有网卡来源上的客户端请求*/serveraddr.sin_addr.s_addr = INADDR_ANY;//绑定,第二个参数需要强转为struct sockaddr*if(bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0){perror("bind error");exit(1);}/*步骤3,调用listen函数启动监听(指定port端口监听)通知系统去接受来自客户端的连接请求,将接收到的客户端连接放在队列中backlog,指定客户端排队队列长度*/if(listen(sockfd, 10) < 0){perror("listen error");exit(1);}/*步骤4,获得某一个客户端的连接/调用accept函数从队列中获得一个 客户端的请求连接,并返回新的socket描述符, 在内部会新创建socket返回描述符,通过此描述符和客户端描述符进行通信,若没有客户端连接,调用accept()会阻塞,直到获得一个客户端连接并返回新的socket描述符第二个参数用于接收客户端地址信息*/struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);//设置线程的分离属性pthread_attr_t attr;pthread_attr_init(&attr); //初始化//设置分离属性pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);while(1){//主控线程负责调用accept去获得客户端连接//会自动往第二个参数里面填入对应的客户端信息,IP,端口//也可从accept()返回的地址描述符获取客户端信息int fd = accept(sockfd, NULL, NULL);if(fd < 0){perror("accept error");continue;//结束本次循环,继续下一次}/*步骤5,启动子线程调用IO函数(read/write)和连接的客户端进行双向的通讯*/pthread_t th;int err;//以分离状态启动子线程//第三个是线程运行函数,第四个是传入运行函数的if((err = pthread_create(&th, &attr, th_fn, (void*)fd)) != 0){perror("pthread create error");}//主控线程摧毁线程属性pthread_attr_destroy(&attr);}return 0;
}

以分离状态启动多线程处理并发运行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/6576.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【工具】批量网址打开器,一次打开多个网址链接

假如你有很多个网址&#xff0c;这些网址要全部打开&#xff0c;你是否会每次复制一个粘贴到浏览器地址栏&#xff0c;再去复制下一个粘贴到地址栏&#xff0c;这样重复的操作&#xff1f;我就有这样的困扰&#xff0c;比如要检查网页上是否有bug&#xff0c;就要一个一个的点开…

「Mac畅玩鸿蒙与硬件24」UI互动应用篇1 - 灯光控制小项目

本篇将带领你实现一个互动性十足的灯光控制小项目&#xff0c;用户可以通过点击按钮来控制灯光的开关。该项目将涉及状态管理、动态图片加载以及按钮交互&#xff0c;是学习鸿蒙应用开发的重要基础。 关键词 UI互动应用状态管理动态图片加载用户交互 一、功能说明 在这个灯光…

如何从0到1开发一款智能生产小工单系统——全网最详细教程!

在生产车间&#xff0c;工单管理一度是个让人头疼的问题。任务分配不清、生产流程混乱、交接环节不顺畅等问题&#xff0c;让管理人员和一线工人疲于奔命。而一个智能的生产小工单系统正是解决这些问题的利器。不仅能有效地理顺工单流转流程&#xff0c;还能大幅提升工作效率和…

SpringBoot基础系列学习(五):JdbcTemplate 访问数据库

文章目录 一丶介绍二丶引入依赖三丶配置配置文件四丶创建表五丶java代码 一丶介绍 Spring Boot作为Spring的集大成者&#xff0c;自然会将JdbcTemplate集成进去。Spring Boot针对JDBC的使用提供了对应的Starter包&#xff1a;spring-boot-starter-jdbc&#xff0c;它其实就是在…

win11电脑无法找到声音输出设备怎么办?查看解决方法

电脑无法找到声音输出设备是一个常见的问题&#xff0c;尤其是在使用Windows操作系统时。幸运的是&#xff0c;大部分问题都可以通过以下几种方法来解决。 一、检查物理连接 在深入诊断之前&#xff0c;首先要检查硬件连接是否正常。这包括&#xff1a; 确保耳机、扬声器或其…

二叉树相关习题

题目&#xff1a;100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; …

数据结构和算法(六):贪心算法、分治算法、回溯算法、动态规划、拓扑排序

从广义上来讲&#xff1a;数据结构就是一组数据的存储结构 &#xff0c; 算法就是操作数据的方法 数据结构是为算法服务的&#xff0c;算法是要作用在特定的数据结构上的。 10个最常用的数据结构&#xff1a;数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie树 10个最…

PPT素材、模板免费下载!

做PPT一定要收藏好这6个网站&#xff0c;PPT模板、素材、图表、背景等超多素材全部免费下载。 1、菜鸟图库 ppt模板免费下载|ppt背景图片 - 菜鸟图库 菜鸟图库网有非常丰富的免费素材&#xff0c;像设计类、办公类、自媒体类等素材都很丰富。PPT模板种类很多&#xff0c;全部都…

Prompt Engineering介绍

什么是Prompt Engineering&#xff1f; 近年来&#xff0c;大语言模型&#xff08;LLM&#xff09;发展迅速&#xff0c;成为自然语言处理领域的重要技术。除了OpenAI的GPT系列、Google的PaLM&#xff08;Pathways Language Model&#xff09;和Bard&#xff0c;国内也涌现出多…

Golang | Leetcode Golang题解之第540题有序数组中的单一元素

题目&#xff1a; 题解&#xff1a; func singleNonDuplicate(nums []int) int {low, high : 0, len(nums)-1for low < high {mid : low (high-low)/2mid - mid & 1if nums[mid] nums[mid1] {low mid 2} else {high mid}}return nums[low] }

【Springboot问题】创建springboot项目后没有Resources文件夹及application文件

问题描述&#xff1a; 在创建springboot项目之后&#xff0c;由于项目识别的问题&#xff0c;没有出现资源文件夹以及application文件。 解决方法&#xff1a; 但是此刻依旧没有application.yml文件&#xff0c;创建

「C/C++」C/C++之 数组赋值给std::vector多种方法

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

2024国际超模大赛亚洲总决赛在成都举行,国外选手对成都印象深刻!

11月3日&#xff0c;2024国际超模大赛亚洲总决赛在成都环球中心洲际酒店成功落下帷幕。从全国2000多名参赛选手中脱颖而出&#xff0c;历经多赛段、多环节层层选拔&#xff0c;晋级的各组获奖选手惊艳亮相总决赛。2024国际超模大赛亚洲总决赛&#xff0c;是一场现象级传播地方特…

Unity3D学习FPS游戏(8)装弹和弹夹UI显示

前言&#xff1a;实现了武器的基本发射功能&#xff0c;但是我们弹夹数量是有限&#xff0c;之前并没有做装弹和弹夹显示的功能。本篇实现装弹和弹夹显示。 装弹和弹夹UI显示 装弹目标思路和实现 弹夹UI显示目标弹夹UI的思路和实现UI代码的思路和实现 武器控制的完整代码效果补…

GameFramework教程☀️福利(五):关于该框架的一些意义

文章目录 📢 不同模式的意义本章探讨GF这样编写的意义和使用场景。 📢 不同模式的意义 最近在做一个app,现在在调研阶段。 代码上后期可能用华佗进行C#热更新。 在调研华佗打包完的热更代码如何和UI AB结合起来时,看到了: "> 从这一点可以延伸理解出,当我们使…

加密货币行业与2024年美国大选

加密货币行业经历了近十年的飞速发展&#xff0c;尤其是在比特币、以太坊等主要加密资产的兴起之后&#xff0c;越来越多的美国人开始将其视为一种财富积累或交易的工具。然而&#xff0c;尽管这一新兴行业的市场规模在持续扩大&#xff0c;但加密货币仍面临着重重监管难题&…

开源 AI 智能名片 2+1 链动模式 S2B2C 商城小程序与私域流量圈层

摘要&#xff1a;本文探讨了私域流量圈层的特点以及其在当今时代的重要性&#xff0c;分析了开源 AI 智能名片 21 链动模式 S2B2C 商城小程序源码在私域流量圈层构建中的作用&#xff0c;阐述了产品在圈层时代被标签化的现象&#xff0c;并以实例展示了如何利用该小程序源码打造…

tinymce扩展功能:1、行高、段落间距、格式刷;2、视频上传进度条;3、对复制的图片设置尺寸

tinymce扩展功能&#xff1a;1、行高、段落间距、格式刷&#xff1b;2、视频上传进度条&#xff1b;3、对复制的图片设置尺寸 一、需求描述二、行高、段落间距、格式刷插件三、实现视频上传的进度条、对复制的图片设置尺寸 一、需求描述 使用技术&#xff1a; vue2 tinymce5.…

【算法篇】--重温算法题

目录 前言【算 一、反转链表 二、合并两个有序链表 三、反转链表II 四、移除链表元素 前言 本篇文章基于学习了一段数据结构&#xff0c;并练习了几道算法题所做的一些笔记&#xff0c;方便日后复习时可以用到。 如果有什么不对的地方&#xff0c;欢迎大家在评论区指正&…

秋叶SD4.9最新版本,解压就能使用,Ai生图超级强大!!!

Midjourney &#xff0c;StableDiffusion&#xff0c;ComfyUI&#xff0c;在AI绘画领域&#xff0c;这3款工具非常有名&#xff0c;Midjourney 这一款对网络有要求&#xff0c;一般可能上不了。SD和ComfyUI是可以本地运行&#xff0c;只要你的电脑配置了8G及以上的独立显卡&…