多进程 - 所有的代码没有写完安全处理, 初学阶段
初学阶段:看到一篇大佬的博客,写得很好,帮助我理解send、recv接口和接收缓冲区、发送缓冲区之间的关系。 下面引用大佬的一些话:
每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工作模式以及TCP的流量(拥塞)控制便是依赖于这两个独立的buffer以及buffer的填充状态。接收缓冲区把数据缓存入内核,应用进程一直没有调用recv()进行读取的话,此数据会一直缓存在相应socket的接收缓冲区内。再啰嗦一点,不管进程是否调用recv()读取socket,对端发来的数据都会经由内核接收并且缓存到socket的内核接收缓冲区之中。recv()所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,并返回,仅此而已。进程调用send()发送的数据的时候,最简单情况(也是一般情况),将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回。换句话说,send()返回之时,数据不一定会发送到对端去(和write写文件有点类似),send()仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中,发送是TCP的事情,和send其实没有太大关系。
原文链接:https://blog.csdn.net/a879365197/article/details/72802364
tcpser.hpp
1 #include<stdio.h>
2 #include<iostream>
3 #include<sys/socket.h>
4 #include<string>
5 #include<string.h>
6 #include<netinet/in.h>
7 #include<unistd.h>
8 #include<arpa/inet.h>
9 #include<stdlib.h>
10
11 //封装tcpert类
12 class TcpSer{
13 public:
14
15 //重命名 - 方便使用
16 typedef struct sockaddr_in sockaddr_in;
17 typedef struct sockaddr sockaddr;
18
19 //构造函数
20 TcpSer()
21 :sockfd_(-1)
22 {
23
24 }
25
26 //析构函数
27 ~TcpSer(){
28
29
30 }
31
32 //socket接口 - ipv4, tcp, 字节流服务
33 bool Socket(){
34
35 sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
36 if(sockfd_ < 0){
37 perror("socket!\n");
38 return false;
39 }
40
41 return true;
42 }
43
44 //bind接口 - 服务器使用
45 bool Bind(const std::string& IP, uint16_t PORT){
46
47 //创建struct sockaddr_in类存放地址信息
48 sockaddr_in addr;
49 addr.sin_family = AF_INET;
50 addr.sin_port = htons(PORT);
51 addr.sin_addr.s_addr = inet_addr(IP.c_str());
52
53 int ret = bind(sockfd_, (sockaddr*)&addr, sizeof(addr));
54 if(ret < 0){
55 perror("bind!");
56 return false;
57 }
58
59 return true;
60 }
61
62 //connect接口 - 客户端使用
63 bool Connect(const char* IP, uint16_t PORT){
64
65 //创建struct sockaddr_in类存放地址信息
66 sockaddr_in peer;
67 peer.sin_family = AF_INET;
68 peer.sin_port = htons(PORT);
69 peer.sin_addr.s_addr = inet_addr(IP);
70
71 int ret = connect(sockfd_, (sockaddr*)&peer, sizeof(peer));
72 if(ret < 0){
73 perror("connect!");
74 return false;
75 }
76
77 return true;
78 }
79
80 //listen接口 - 服务器使用
81 bool Listen(const int backlog = 5){
82
83 int ret = listen(sockfd_, backlog);
84 if(ret < 0){
85 perror("listen!");
86 return false;
87 }
88
89 return true;
90 }
91
92 //accept接口 - 服务器使用 - 需要接受用户端的地址信息 -
93 //采用出参保存, 还需要创建TcpSer类来保留交互的fd
94 bool Accept(TcpSer* ts, sockaddr_in& peeraddr){
95
96 socklen_t addrlen = sizeof(peeraddr);
97
98 int ret = accept(sockfd_, (sockaddr*)&peeraddr, &addrlen);
99 if(ret < 0){
100 perror("accept!");
101 return false;
102 }
103
104 ts->sockfd_ = ret;
105
106 return true;
107 }
108
109 //send接口
110 bool Send(const std::string& Buf){
111
112 int write_size = send(sockfd_, Buf.c_str(), Buf.size(), 0);
113
114 if(write_size < 0){
115 perror("send!");
116 return false;
117 }
118
119 return true;
120 }
121
122 //recv接口
123 bool Recv(std::string& Buf){
124
125 char buf[1024 * 10] = {0};
126
127 int read_size = recv(sockfd_, buf , sizeof(buf) - 1, 0);
128
129 if(read_size < 0){
130 perror("read_size!");
131 return false;
132 }
133 else if(0 == read_size){
134 printf("peer close this connect!\n");
135 return false;
136 }
137
138 Buf.assign(buf, read_size);
139 return true;
140 }
141
142 //释放fd接口 - 系统调用函数
143 void Close(){
144 close(sockfd_);
145 sockfd_ = -1;
146 }
147 private:
148 int sockfd_;
149 };
多进程实现服务器端
1 #include"tcpser.hpp"
2 #include<signal.h>
3 #include<sys/wait.h>
4
5 //使用更改信号默认处理函数 - wait回收子进程资源。
6 void sigcb(int signum){
7 (void)signum;
8 wait(NULL);
9 }
10
11 int main(int argc, char* argv[]){
12 //使用命令行参数来赋值ip、port
13 if(argc != 3){
14 printf("未输入参数IP&PORT!\n");
15 return 0;
16 }
17
18 //更该SIG_CHLD17信号的处理方式 - 防止僵尸进程
19 signal(17, sigcb);
20 //IP、
21 uint16_t PORT = atoi(argv[2]);
22 std::string IP = argv[1];
23
24 TcpSer ts;
25 //socket
26 if(!ts.Socket()){
27 perror("socket failed!");
28 return 0;
29 }
30 //bind 绑定地址信息
31 if(!ts.Bind(IP, PORT)){
32 perror("bind failed!");
33 return 0;
34 }
35 //listen 监听接口
36 if(!ts.Listen()){
37 perror("Listen!");
38 return 0;
39 }
40
41 printf("服务器端已启动!\n");
42 //服务器处理主流程
43 while(1){
44 //申请存放用户交互的fd类 和 客户端地址信息
45 TcpSer* peerts = new TcpSer;
46 sockaddr_in peeraddr;
47 //获得三次握手的请求并返回交互的fd
48 if(!ts.Accept(peerts, peeraddr)){
49 perror("accept!\n");
50 return 0;
51 }
52 //进程处理流程
53 int ret = fork();
54
55 if(ret < 0){ //子进程创建失败
56 perror("fork!");
57 return 0;
58 }
59 else if(0 == ret){
60 //子进程处理流程
61 printf("ser have a new connect, ip:port ---> %s:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
62
63 while(1){
64 std::string Buf;
65
66 if(!peerts->Recv(Buf)){
67 continue;
68 }
69
70 printf("client respond: [%s]\n", Buf.c_str());
71
72 printf("serve respond:");
73 fflush(stdout);
74 std::getline(std::cin, Buf);
75
76 if(!peerts->Send(Buf)){
77 continue;
78 }
79 }
80 //交互完毕之后,释放peerts资源 和 交互的fd
81 peerts->Close();
82 delete peerts;
83 exit(0);
84 }
85
86 }
87 //主进程释放监听的fd
88 ts.Close();
89 return 0;
90 }
多线程实现服务器端
1 /*
2 //采用多线程实现的服务器
3 */
4
5 #include"tcpser.hpp"
6 #include<pthread.h>
7
8
9 //线程入口函数
10 void* thread_start(void* arg)
11 {
12 //线程分离 - 结束后由os释放内存资源
13 pthread_detach(pthread_self());
14
15 //获取操作的类 - ts
16 TcpSer* ts = (TcpSer*)arg;
17
18
19 //服务器端执行流程
20 while(1){
21 std::string Buf;
22
23 //recv接口 - 从接受缓冲区读取数据
24 if(!ts->Recv(Buf))
25 continue;
26 printf("client says: [%s]\n", Buf.c_str());
27
28 printf("serve respond:");
29 fflush(stdout);
30 std::getline(std::cin, Buf);
31
32 //send接口 - 向发送缓冲区发送数据
33 if(!ts->Send(Buf))
34 continue;
35
36 printf("发送数据成功!\n");
37 }
38
39 //关闭socketfd
40 ts->Close();
41 //这块的ts是从堆上开辟的, 我们让每个线程自己取释放, 可以事半功倍!
42 delete ts; //释放开辟的内存 -- 很关键
43 }
44
45
46 //服务器端的主流程
47 int main(int argc, char* argv[]){
48
49 //接受IP、PORT - 未提供接口获得,简单测试
50 if(argc != 3){
51 printf("未输入参数IP&PORT!\n");
52 return 0;
53 }
54
55 //IP、 PORT
56 uint16_t PORT = atoi(argv[2]);
57 std::string IP = argv[1];
58
59
60 //监听ts
61 TcpSer ts;
62
63 //socket
64 if(!ts.Socket()){
65 perror("socket failed!");
66 return 0;
67 }
68
69 //bind - 绑定信息
70 if(!ts.Bind(IP, PORT)){
71 perror("bind failed!");
72 return 0;
73 }
74
75 //listen - 监听接口, 由内核维护这块代码 - 当传入新的三次握手成功的请求,就会将该请求放到完成的队列当中
76 //等到accept接口获取。
77 if(!ts.Listen()){
78 perror("Listen!");
79 return 0;
80 }
81
82 printf("服务器端已启动!\n");
83
84 //serve主线程流程
85 while(1){
86
87 //堆上开辟peerts - 操控peerts->ts
88 TcpSer* peerts = new TcpSer;
89
90 //保留客户端的地址信息 - 以出参形式
91 sockaddr_in peeraddr;
92
93 //accept接口, 获取三次握手的请求,并返回新的操作句柄-fd
94 if(!ts.Accept(peerts, peeraddr)){
95 perror("accept!\n");
96 return 0;
97 }
98
99 printf("ser has new connection - ip[%s], port[%d]\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
100
101 //调用线程执行流程
102 pthread_t tid;
103 int ret = pthread_create(&tid, NULL, thread_start, (void*)peerts);
104
105 if(ret){
106 perror("pthread_create!");
107 return 0;
108 }
109
110 }
111
112 //释放监听socket
113 ts.Close();
114 return 0;
115 }
以上是简单实现多进程、多线程tcp通信, 里面还有很多缺点(例如:采用while死循环无法正常结束等等。)