1.tcp协议:传输控制协议---面向连接,传输可靠,面向字节流。
实现数据可靠传输,传输灵活但是会造成数据粘包问题。
2.tcp客户端与服务端通信流程
3.代码实现
//客户端
1 #include"tcpsocket.hpp"
2 #include<signal.h>
3
4 void sigcb(int signo)
5 {
6 printf("connection closed\n");
7 }
8
9 int main(int argc, char* argv[])
10 {
11 if(argc != 3)
12 {
14 return -1;
15 }
16 std::string ip = argv[1];
17 uint16_t port = atoi(argv[2]);
18
19 signal(SIGPIPE, sigcb);
20 TcpSocket sock;
21 CHECK_RET(sock.Socket());
22 CHECK_RET(sock.Connect(ip, port));
23 while(1)
24 {
25 std::string buf;
26 std::cout<<"client say: ";
27 fflush(stdout);
28 std::cin>>buf;
29 sock.Send(buf);
30 buf.clear();
31 sock.Recv(buf);
32 std::cout<<"server say: "<<buf<<std::endl;
33 }
34 sock.Close();
35 return 0;
36 }
//服务端
1 #include"tcpsocket.hpp"
2 int main(int argc, char* argv[])
3 {
4 if(argc != 3)
5 {
7 return -1;
8 }
9 std::string ip = argv[1];
10 uint16_t port = atoi(argv[2]);
11 TcpSocket sock;
12
13 CHECK_RET(sock.Socket());
14 CHECK_RET(sock.Bind(ip, port));
15 CHECK_RET(sock.Listen());
16 while(1)
17 {
18 TcpSocket clisock;
19 std::string cliip;
20 uint16_t cliport;
21 if(sock.Accept(clisock, cliip, cliport) == false)
22 {
23 continue;
24 }
25
26 std::cout<<"new client: "<<cliip<<":"<<cliport<<std::endl;
27 while(1)
28 {
29 std::string buf;
30 clisock.Recv(buf);
31 std::cout<<"client say: "<<buf<<std::endl;
32 buf.clear();
33 std::cout<<"server say: ";
34 fflush(stdout);
35 std::cin>>buf;
36 clisock.Send(buf);
37 }
38 }
39 sock.Close();
40 return 0;
41 }
//封装好各个函数的头文件
1 #include<iostream>
2 #include<stdio.h>
3 #include<string>
4 #include<unistd.h>
5 #include<stdlib.h>
6 #include<errno.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9 #include<sys/socket.h>
10 #define CHECK_RET(q) if((q) == false) { return -1;}
11
12 class TcpSocket
13 {
14 public:
15 TcpSocket(){}
16 ~TcpSocket(){}
17 bool Socket()
18 {
19 _socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
20 if(_socketfd < 0)
21 {
22 perror("socket error");
23 return false;
24 }
25 return true;
26 }
27
28 bool Bind(std::string& ip, uint16_t port)
29 {
30 struct sockaddr_in addr;
31 addr.sin_family = AF_INET;
32 addr.sin_port = htons(port);
33 addr.sin_addr.s_addr = inet_addr(ip.c_str());
34
35 socklen_t len = sizeof(struct sockaddr_in);
36 int ret = bind(_socketfd, (struct sockaddr*)&addr, len);
37 if(ret < 0)
38 {
39 perror("bind error");
40 return false;
41 }
42 return true;
43 }
44
45 bool Listen(int backlog = 5)
46 {
47 int ret = listen(_socketfd, backlog);
48 if(ret < 0)
49 {
50 perror("listen error");
51 return false;
52 }
53 return true;
54 }
55
56 bool Accept(TcpSocket &cli, std::string &cliip, uint16_t port)
57 {
58 struct sockaddr_in addr;
59 socklen_t len = sizeof(struct sockaddr_in);
60 int sockfd = accept(_socketfd, (struct sockaddr*)&addr, &len);
61 if(sockfd < 0)
62 {
63 perror("accept error");
64 return false;
65 }
66 cli.SetFd(sockfd);
67 cliip = inet_ntoa(addr.sin_addr);
68 port = ntohs(addr.sin_port);
69 return true;
70 }
71
72 void SetFd(int sockfd)
73 {
74 _socketfd = sockfd;
75 }
76
77 bool Connect(std::string& srv_ip, uint16_t srv_port)
78 {
79 struct sockaddr_in addr;
80 addr.sin_family = AF_INET;
81 addr.sin_port = htons(srv_port);
82 addr.sin_addr.s_addr = inet_addr(srv_ip.c_str());
83 socklen_t len = sizeof(struct sockaddr_in);
84
85 int ret = connect(_socketfd, (struct sockaddr*)&addr, len);
86 if(ret < 0)
87 {
88 perror("connect error");
89 return false;
90 }
91 return true;
92 }
93
94 bool Send(std::string &buf)
95 {
96 int ret = send(_socketfd, buf.c_str(), buf.size(), 0);
97 if(ret < 0)
98 {
99 perror("send error");
100 return false;
101 }
102 return true;
103 }
104
105 bool Recv(std::string &buf)
106 {
107 char temp[1024];
108 int ret = recv(_socketfd, temp, 1024, 0);
109 if(ret < 0)
110 {
111 perror("recv error");
112 return false;
113 }
114 buf.assign(temp, ret);
115 return true;
116 }
117
118 bool Close()
119 {
120 close(_socketfd);
121 return true;
122 }
123 private:
124 int _socketfd;
125
126 };
4.代码中的几个函数:
htons():表示short型数据从主机字节序转到网络字节序。
h:host(主机); n:net(网络); s:short(短整型)/ l:long(长整型)
同理还有ntohs() 、htonl() 、htonl()。
inet_addr():将字符串点分十进制转化成网络字节序IP地址。
inet_ntos():将网络字节序转换成点分十进制形式IP地址。
listen():声明socket处于监听状态,其第二个参数为允许的客户端最大并发连接数。
accept():从已完成连接队列中获取一个客户端新建的已完成连接的socket,如果没有,则阻塞等待。
bind():客户端没有调用bind()函数,因为如果在同一个机器上启动多个客户端,就会出现端口号被占用导致不能正确建立连接。
recv():返回0时,一定是tcp断开连接,而不是传输数据大小为0,recv返回0,send触发异常。
5.使用上面的代码再启动一个客户端,尝试连接服务器,会发现第二个客户端不能正确与服务端通信,这是因为我们accept了一个请求之后,就一直在while循环尝试读取数据,没有继续调用accept导致不能接受新的请求。
所以使用多进程/多线程版本修改tcp的服务端程序即可。
//多进程版本服务端
#include <signal.h>
#include <sys/wait.h>
#include "tcpsocket.hpp"
void sigcb(int no){
while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char *argv[])
{
if (argc != 3) {
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
signal(SIGCHLD, sigcb);
TcpSocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Bind(ip, port));
CHECK_RET(sock.Listen());
while(1) {
TcpSocket clisock;
std::string cliip;
uint16_t cliport;
if (sock.Accept(clisock, cliip, cliport) == false){
continue;
}
std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl;
int pid = fork();
if (pid == 0) {
while(1) {
std::string buf;
clisock.Recv(buf);
std::cout<<"client say:"<<buf<<std::endl;
buf.clear();
std::cout<< "server say:";
fflush(stdout);
std::cin >> buf;
clisock.Send(buf);
}
clisock.Close();
exit(0);
}
//父进程一定要关闭这个套接字;因为父子进程数据独有
//父进程关闭对子进程无影响;不关闭会造成资源泄漏
clisock.Close();
}
sock.Close();
}
//多线程版本服务端
#include <pthread.h>
#include "tcpsocket.hpp"
void *thr_start(void *arg)
{
TcpSocket *clisock = (TcpSocket*)arg;
while(1) {
std::string buf;
clisock->Recv(buf);
std::cout<<"client say:"<<buf<<std::endl;
buf.clear();
std::cout<< "server say:";
fflush(stdout);
std::cin >> buf;
clisock->Send(buf);
}
clisock->Close();
delete clisock;
return NULL;
}
int main(int argc, char *argv[])
{
if (argc != 3) {
return -1;
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
TcpSocket sock;
CHECK_RET(sock.Socket());
CHECK_RET(sock.Bind(ip, port));
CHECK_RET(sock.Listen());
while(1) {
TcpSocket *clisock = new TcpSocket();
std::string cliip;
uint16_t cliport;
if (sock.Accept(*clisock, cliip, cliport) == false){
continue;
}
std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl;
pthread_t tid;
pthread_create(&tid, NULL, thr_start, (void*)clisock);
pthread_detach(tid);
}
sock.Close();
}
在多线程版本中,由于在线程之间共享文件描述符,一个线程中打开的文件,另一个线程只要获取到这个文件的描述符,也可以对其进行操作。