tcp的详细细节后面讲解,先来用它的一些接口实现1个简单的通信。下面来看它的一套接口
功能:socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;应用程序可以像读写文件一样用read/write在网络上收发数据;
函数原型:
int socket(int domain, int type, int protocol);
参数说明:
返回值:
成功则返回socket文件描述符,错误返回-1.
socket接口在上一篇的udp中已经有了,这里不做多余的讲解。
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号.
函数原型:
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
参数说明:
返回值:
前面的这2个接口的使用跟udp是一样的。
listen声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多
的连接请求就忽略, 这里设置不会太大(一般是5)。udp完成bind后就可以接收和发送信息了,tcp在这里就跟它不同了。为什么要监听呢?因为服务器要知道从客户端发来了请求。
就像在公司的食堂吃饭一样,我们上面时候去都能吃到饭,因为食堂的打饭人员一看到人来就知道有人来吃饭了。这个打饭人员就是处于监听状态,我们吃饭就是客户端的请求。
函数原型:
int listen(int sockfd, int backlog);
参数说明:
返回值:成功返回0,失败返回-1
服务器需要接收客户端的请求,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:
返回值:(重点理解返回值)
成功返回新的套接字,失败返回-1
为什么返回1个套接字?
来看个例子:
这个accept的返回值就是这个服务员,sockfd就是这个拉客的,吃饭的人就是客户端发来的请求。
这个拉客的把人带给服务员后,她有继续出去拉客了。sockfd这个套接字继续拉客也就是继续从底层获取客户端的链接,服务员专门处理链接也就是返回的这个套接字,专门处理和用户沟通。
我们的客户端需要调用connet来连接服务器。
函数原型:
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数说明:
connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
connect()成功返回0,出错返回-1
功能接收数据。
函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明:
返回值:
成功则返回读出来的大小,失败返回-1.
功能发送信息,函数原型
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数说明:
下面来利用上面的接口来写个简单服务器和客户端来实现通信,首先是单进程版的。
服务器
整体逻辑:
tcpServer.hpp
1 #pragma once
2 #include<iostream>
3 #include<cstdio>
4 #include<stdlib.h>
5 #include<cstring>
6 #include<unistd.h>
7 #include<sys/types.h>
8 #include<sys/socket.h>
9 #include<arpa/inet.h>
10 #include<netinet/in.h>
11 #define BACKLOG 5
12 class tcpServer
13 {
14 private:
15 int port;
16 int lsock;
17 public:
18 tcpServer(int _port = 8080)
19 :port(_port)
20 ,lsock(-1)
21 {}
22 void initServer()
23 {
24 lsock = socket(AF_INET,SOCK_STREAM,0);
25 struct sockaddr_in local;
26 local.sin_family = AF_INET;//IPv4
27 local.sin_port = htons(port);//端口号,主机序列转成网络序列
28 local.sin_addr.s_addr = INADDR_ANY;//ip地址
29
30 //开始绑定
31 if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)
32 {
33 std::cerr<<"bind error"<<std::endl;
34 exit(2);
35 }
36 //开始监听
37 if(listen(lsock,BACKLOG) < 0)
38 {
39 std::cerr<<"listen error"<<std::endl;
40 exit(3);
41 }
42 }
43 //服务
44 void server(int sock)
45 {
46 char buffer[1024];
47 while(true)
48 {
49 //先将缓冲区的信息读出来
50 ssize_t s = recv(sock,buffer,sizeof(buffer)-1,0);
51 if(s > 0)
52 {
53 buffer[s] = 0;
54 std::cout<<"Client:"<<buffer<<std::endl;
55 //再给客户端发送信息
56 send(sock,buffer,strlen(buffer),0);
57 }
58 //s == 0,服务器要知道客户端退出
59 else if(s == 0)
60 {
61 std::cout<<"Client quit..."<<std::endl;
62 break;
63 }
64 else
65 {
66 std::cout<<"recv error"<<std::endl;
67 break;
68 }
69 }
70 close(sock);
71 }
72 void start()
73 {
74 struct sockaddr_in endpoint;
75 while(true)
76 {
77 //先接收客户端的信息
78 socklen_t len = sizeof(endpoint);
79 int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
80 if(sock < 0)
81 {
82 std::cerr<<"accept error"<<std::endl;
83 continue;
84 }
85 std::cout<<"get new a link"<<std::endl;
86 server(sock);
87 }
88 }
89 ~tcpServer()
90 {
91 close(lsock);
92 }
93
94 };
tcpServer.cc
1 #include"tcpServer.hpp"
2
3 void Usage(std::string proc)
4 {
5 std::cout<<"Usage:"<<proc<<"port"<<std::endl;
6 }
7 int main(int argc,char* argv[])
8 {
9 if(argc != 2)
10 {
11 Usage(argv[0]);
12 exit(1);
13 }
14 tcpServer *ts = new tcpServer(atoi(argv[1]));
15 ts->initServer();
16 ts->start();
17 delete ts;
18 return 0;
19 }
客户端
客户端先写,再发给服务器,最后接收服务器的信息。
tcpClient.hpp
1 #pragma once
2
3 #include<iostream>
4 #include<stdlib.h>
5 #include<unistd.h>
6 #include<sys/types.h>
7 #include<sys/socket.h>
8 #include<netinet/in.h>
9 #include<arpa/inet.h>
10
11 class tcpClient
12 {
13 private:
14 std::string serip;
15 int serport;
16 int sock;
17 public:
18 tcpClient(std::string _serip = "172.0.0.1",int _serport = 8080)
19 :serip(_serip)
20 ,serport(_serport)
21 {}
22 void initClient()
23 {
24 sock = socket(AF_INET,SOCK_STREAM,0);
25 if(sock < 0)
26 {
27 std::cerr<<"sock error"<<std::endl;
28 exit(1);
29 }
30 struct sockaddr_in ser;
31 ser.sin_family = AF_INET;
32 ser.sin_port = htons(serport);
33 ser.sin_addr.s_addr = inet_addr(serip.c_str());
34 //链接服务器
35 if(connect(sock,(struct sockaddr*)&ser,sizeof(ser)) < 0)
36 {
37 std::cerr<<"connect error"<<std::endl;
38
39 }
40 }
41 void start()
42 {
43 char msg[128];
44 while(true){
45 //用read读,udp不能使用read
46 std::cout<<"please enter#";
47 fflush(stdout);
48 //0是标准输入,从标准输入开始读
49 size_t s = read(0,msg,sizeof(msg)-1);
50 if(s > 0)
51 {
52 msg[s-1] = 0;
53 //发送给服务器
54 send(sock,msg,sizeof(msg)-1,0);
55 //接收服务器的信息
56 size_t ss = recv(sock,msg,sizeof(msg)-1,0);
57 if(ss > 0)
58 {
59 msg[ss] = 0;
60 std::cout<<"server #"<<msg<<std::endl;
61 }
62 }
63
64 }
65 }
66 ~tcpClient()
67 {
68 close(sock);
69 }
70 };
tcpClient.cc
37 std::cerr<<"connect error"<<std::endl;
38
39 }
40 }
41 void start()
42 {
43 char msg[128];
44 while(true){
45 //用read读,udp不能使用read
46 std::cout<<"please enter#";
47 fflush(stdout);
48 //0是标准输入,从标准输入开始读
49 size_t s = read(0,msg,sizeof(msg)-1);
50 if(s > 0)
51 {
52 msg[s-1] = 0;
53 //发送给服务器
54 send(sock,msg,sizeof(msg)-1,0);
55 //接收服务器的信息
56 size_t ss = recv(sock,msg,sizeof(msg)-1,0);
57 if(ss > 0)
58 {
59 msg[ss] = 0;
60 std::cout<<"server #"<<msg<<std::endl;
61 }
62 }
63
64 }
65 }
66 ~tcpClient()
67 {
68 close(sock);
69 }
70 };
1.父进程创建子进程,子进程再创建子进程,子进程立即退出,父进程可以立即等待到子进程,孙子进程去执行服务,完成后成孤儿进程由系统领养。
75 void start()
76 {
77 struct sockaddr_in endpoint;
78 while(true)
79 {
80 //先接收客户端的信息
81 socklen_t len = sizeof(endpoint);
82 int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
83 if(sock < 0)
84 {
85 std::cerr<<"accept error"<<std::endl;
86 continue;
87 }
88 std::string ip = inet_ntoa(endpoint.sin_addr);
89 int port = ntohs(endpoint.sin_port);
90 std::cout<<"get new a link..."<<ip<<":"<<port<<"sock"<<sock<<std::endl;
91 pid_t id = fork();
92 if(id == 0)
93 {
94 close(lsock);
95 if(fork() > 0)
96 {
97 exit(0);
98 }
99 server(sock);
100 exit(0);
101 }
102 close(sock);//父进程要关闭多余的sock,
103 waitpid(id,nullptr,0);
104 }
105 }
2.父进程忽略子进程
在初始化的时候加上:
signal(SIGCHLD,SIG_IGN);
91 pid_t id = fork();
92 if(id == 0)
93 {
94 server(sock);
95 exit(0);
96 }
97 close(sock);//父进程要关闭多余的sock,
98 }
99 }
while :; do ps axj | head -1 && ps axj|grep -E 'tcpServer|tcpClient'|grep -v grep ;echo "#####################";sleep 1;done
总共链接了3次服务器,由于客户端和服务器是在我的同一台电脑上,所以是3个服务器,3个客户端。
多进程太吃资源了,所以下面来用多线程。
46 static void* handle(void* arg)
47 {
48 pthread_detach(pthread_self());//线程分离
49 int *p = (int*) arg;
50 int sock = *p;
51 server(sock);
52 delete p;
53 }
83 void start()
84 {
85 struct sockaddr_in endpoint;
86 while(true)
87 {
88 //先接收客户端的信息
89 socklen_t len = sizeof(endpoint);
90 int sock = accept(lsock,(struct sockaddr*)&endpoint,&len);
91 if(sock < 0)
92 {
93 std::cerr<<"accept error"<<std::endl;
94 continue;
95 }
96 std::string ip = inet_ntoa(endpoint.sin_addr);
97 int port = ntohs(endpoint.sin_port);
98 std::cout<<"get new a link..."<<ip<<":"<<port<<".."<<"sock"<<sock<<std::endl;
99 tcpServer* p = new tcpServer(sock);
100 pthread_t tid;
101 pthread_create(&tid,nullptr,handle,p);
115 }
116 }
小结
后面博主会更新线程池的版本来解决问题。