项目 | 特点 |
---|---|
UDP | 用户数据报协议,无需连接,不可靠,面向数据报,其实时性>可靠性(典型的就是视频传输) |
TCP | 传输控制协议,面向连接,可靠,面向字节流,其可靠性>实时性(文件传输) |
1 #include <cstdio>
2 #include <iostream>
3 #include <string>
4 #include <unistd.h>
5 #include <arpa/inet.h>
6 #include <netinet/in.h>
7 #include <sys/socket.h>
8 //dup网络协议类
9 //需要实现5个接口,
10 //1.创建套接字
11 //2.绑定地址信息(一般是服务器端需要)
12 //3.发送数据
13 //4.接受数据
14 class UDPSocket{
15 //套接字的成员变量就是套接字操作句柄
16 private:
17 int _sockfd;
18
19 public:
20
21 UDPSocket()
22 :_sockfd(-1){
}
23 //创建套接字
24 //socket(地址域类型,套接字类型,本次通信协议)
25 bool Socket(){
26 _sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
27 if(_sockfd<0){
28 perror("socket error");
29 return false;
30 }
31 return true;
32 }
33
34
35 //绑定地址信息(套接字操作句柄,addr结构体地址信息指针,地址信息长度)
36 //不同的通讯协议地址信息大小不同
37 //统一的接口是sockaddr(为了接口规范)16字节
38 //ipv4是sockaddr_in 16字节
39 //封装的接口需要ip地址和port值
40 bool Bind(const std::string &ip,uint16_t port){
41 struct sockaddr_in addr;
42 addr.sin_family=AF_INET;
43 addr.sin_port=htons(port);
44 addr.sin_addr.s_addr=inet_addr(ip.c_str());
45 socklen_t len=sizeof(struct sockaddr_in);
46 int ret;
47 //第二参数要穿地址!!
48 ret=bind(_sockfd,(struct sockaddr*)&addr,len);
49 if(ret<0){
50 perror("bind error");
51 return false;
52 }
53 return true;
54 }
55
56
57 //发送数据接口
58 //(套接字操作句柄,发送空间首地址,数据长度,0阻塞,对端地址信息,对端地址结构长度)
59 //封装的接口要数据的地址信息,对端ip和port
60 //注意ip是const,可以直接以"192.0.0.0"格式发送,否则只能是string类型
61 //因为"192.0.0.0"是常量字符串
62 bool Send(std::string& data,const std::string &ip,u_int16_t port){
63 struct sockaddr_in addr;
64 addr.sin_family=AF_INET;
65 addr.sin_port=htons(port);
66 addr.sin_addr.s_addr=inet_addr(ip.c_str());
67 socklen_t len=sizeof(struct sockaddr_in);
68 int ret=sendto(_sockfd,data.c_str(),data.size(),0,(sockaddr*)&addr,len);
69 if(ret<0){
70 perror("sendto error");
71 return false;
72 }
73 return true;
74 }
75
76 //接受数据(操作句柄,接受空间地址,接收数据长度,0阻塞,(输出参数)发送源端地址信息,(输入输出型参数)指定接收的地质结构大小,返回实际的大小)
77 ///封装的接口是接受地址,接收发送方ip和port的地址(一般情况可以不接受,指定为NULL表示不需要直到发送方的信息)
78 bool Recv(std::string * buf,std::string* ip=NULL,int * port=NULL){
79 struct sockaddr_in addr;
80 socklen_t len=sizeof(struct sockaddr_in);
81 char tmp[1024]={
0};
82 int ret=recvfrom(_sockfd,tmp,1024,0,(sockaddr*)&addr,&len);
83 if(ret<0){
84 perror("sendto error");
85 return false;
86 }
87 //ret为世界接收到的数据长度
88 buf->assign(tmp,ret);
89
90 if(ip!=NULL){
91 *ip=inet_ntoa(addr.sin_addr);
92 }
93 if(port!=NULL){
94 *port=ntohs(addr.sin_port);
95 }
96 return true;
97 }
98
99 //关闭套接字
100 bool Close(){
101 if(_sockfd!=-1){
102 close(_sockfd);
103 }
104 return true;
105 }
106
107 };
1 #include"classudp.hpp"
2
3 #define CHECK_RET(q) if((q)==false){
return -1;}
4 //服务端需要做的步骤:
5 //1.创建套接字
6 //2.绑定地址信息
7 //3.接收数据,(如果需要反馈给客户端则需要记录地址信息)
8 //4.发送数据
9 int main(){
10 UDPSocket ssock;
11 CHECK_RET(ssock.Socket());
12 CHECK_RET(ssock.Bind("192.168.107.128",9000));
13 while(1){
14 std::string buf;
15 int cliport;
16 std::string clip;
17 CHECK_RET(ssock.Recv(&buf,&clip,&cliport));
18 std::cout<<"client say: "<<buf<<std::endl;
19 buf.clear();//清空缓冲区
20 std::cout<<"server say: ";
21 std::cin>>buf;
22 CHECK_RET(ssock.Send(buf,clip,cliport));
23 }
24 ssock.Close();
25 return 0;
26 }
1 #include "classudp.hpp"
2
3 #define CHECK_RET(q) if((q)==false){
return -1;}
4 //客户端的任务流程
5 //1.创建套接字
6 //2.绑定地址结构
7 //3.发送数据
8 //4.接受数据
9 //5.关闭套接字
10 int main(){
11 //1.
12 UDPSocket sock;
13 CHECK_RET(sock.Socket());
14 //2.
15 //3.
16 while(1){
17 std::cout<<"client saY:";
18 std::string buf;
19 std::cin>>buf;
20 CHECK_RET(sock.Send(buf,"192.168.107.128",9000));
21
22
23 //4.
24 buf.clear();//不用的缓冲区清空
25 CHECK_RET(sock.Recv(&buf));//用发送缓冲区接收,不接收源端信息就不写
26 std::cout<<"server say: "<<buf<<std::endl;
27 }
28 //5.
29 sock.Close();
30 return 0;
31 }
1 #include <cstdio>
2 #include <iostream>
3 #include <string>
4 #include <unistd.h>
5 #include <arpa/inet.h>
6 #include <netinet/in.h>
7 #include <sys/socket.h>
8
9 #define LISTEN_MAX 5
10 #define CHECK_RET(q) if((q)==false){
return -1;}
11 //tcp的流程大致如下“
12 //客户端:创建套接字,不推荐绑定地址信息,向服务器发送链接请求,收发数据,关闭套接字
13 //服务端:创建套接字,绑定地址信息,开始监听,获取新建连接,收发数据,关闭套接字
14 class TCPSocket{
15 private:
16 int _sockfd;
17 public:
18 TCPSocket():_sockfd(-1){
}
19 //创建套接字
20 //socket(地址域类型,套接字类型,通信协议)
21 //封装的接口需要获取操作句柄
22 bool Socket(){
23 _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
24 if(_sockfd<0){
25 perror("socket error");
26 return false;
27 }
28 return true;
29 }
30
31 //绑定地址信息
32 //接口(句柄,规范接口struct结构体,结构体大小)、
33 //封装的接口(ip地址,port信息)
34 bool Bind(const std::string &ip,const uint16_t port){
35 sockaddr_in addr;
36 addr.sin_family = AF_INET;//16位地址类型
37 addr.sin_port = htons(port);
38
39 //这两种都能拿到ip内容(str的首地址)
40 addr.sin_addr.s_addr = inet_addr(&ip[0]);
41 //addr.sin_addr.s_addr = inet_addr(ip.c_str());
42
43 socklen_t len = sizeof(sockaddr_in);
44 int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
45
46 if (ret < 0) {
47 perror("bind error");
48 return false;
49 }
50 return true;
51
52 }
53
54
55 //监听接口(操作句柄,最大连接数)
56 //封装接口(最大连接数)
W> 57 bool Listen(int big =LISTEN_MAX){
58 int ret=listen(_sockfd,LISTEN_MAX);
59 if(ret<0){
60 return false;
61 }
62 return true;
63 }
64
65
66
67 //申请链接(操作句柄,服务端的地址,地址长度)
68 //封装的接口(服务器ip,服务器port)
69 bool Connect(const std::string &ip,uint16_t port){
70 sockaddr_in addr;
71 addr.sin_family = AF_INET;
72 addr.sin_port = htons(port);
73 addr.sin_addr.s_addr = inet_addr(&ip[0]);
74 socklen_t len = sizeof(sockaddr_in);
75 int ret = connect(_sockfd, (sockaddr*)&addr, len);
76 if (ret < 0) {
77 perror("connect error");
78 return false;
79 }
80 return true;
81 }
82
83 //封装的接口:
84 //注意三个都是输出型参数(因为是处理请求的接口,所以不需要输入什么信息,只需要获得)
85 //服务器同意链接接口(新的套接字地址,客户端的地址结构指针,地址结构大小指针)
86 //注意这个的前提是客户端发送请求了,所以后两个数输出结构,用来获取请求的客户端的地址信息,当然可以不获取
87
88 //原始的的接口(监听套接字,获取客户端的ip,客户端的port地址)
89 //返回值是一个新的套接字,专门用来服务客户端,所以监听套接字相当于门迎~
90 //将返回值放到封装的接口参数1,原始接口2是输出参数,分开封装到封装接口的23
91 bool Accept(TCPSocket *sock,std::string *ip=NULL,uint16_t *port=NULL){
92 //建立一个结构体用来存客户端的地址信息
93 sockaddr_in addr;
94 socklen_t len = sizeof(sockaddr_in);
95
96 //注意这个len是输出型参数,所以是取地址!!!!
97 int newfd=accept(_sockfd,(sockaddr*)&addr,&len);
98 if (newfd < 0) {
99 perror("accept error");
100 return false;
101 }
102
103 //将输出型参数1填复制的句柄
104 sock->_sockfd = newfd;
105 if (ip != NULL) {
106 *ip = inet_ntoa(addr.sin_addr);
107 }
108 if (port != NULL) {
109 *port = ntohs(addr.sin_port);
110 }
111 return true;
112
113 }
114
115
116 //接收数据(操作句柄,接收缓冲区,接收数据长度,0阻塞)
117 //封装的接口(接收缓冲区)
118 //注意返回值小于0错误
119 //等于0连接断开
120 //大于0,实际大小
121 bool Recv(std::string *buf){
122 char tmp[1024]={
0};
123 int ret=recv(_sockfd,tmp,1024,0);
124 if (ret < 0) {
125 perror("recv error");
126 return false;
127 }
128 else if (ret == 0) {
129
130 printf("peer shutdown");
131 return false;
132 }
133 //把缓冲区的内容放入真正的缓冲区
134 //因为原始接口需要指定一次接受的数据大小
135 //而直接用缓冲区接收不能确定大小
136 buf->assign(tmp, ret);
137 return true;
138 }
139
140 //发送数据(描述符,数据,长度,标志位0阻塞)
141 //封装的接口(要发送数据)
142 //返回实际的长度
143 //这里需要考虑一次发送不完全部数据该怎么办
144 bool Send(const std::string &data){
145 int total;
W>146 while(total < data.size()){
147
148 int ret = send(_sockfd, &data[0] + total,data.size() - total, 0);
149 if(ret<0){
150
151 perror("send error");
152 return false;
153 }
154 total+=ret;
155 }
156 return true;
157 }
158
159 bool Close(){
160 if (_sockfd != -1) {
161 close(_sockfd);
162 }
163 return true;
164 }
165
166 };
1 #include<iostream>
2 #include<string>
3 #include"classtcp.hpp"
4 #define CHECK_RET(q) if((q)==false){
return -1;}
5 //我们用程序运行参数来直接绑定服务器的地址信息
6 //argv[0]就是这个程序的运行 argv[1]ip地址 argv[2]port号
7 int main(int argc,char *argv[]){
8 if (argc != 3) {
9 printf("error\n");
10 return -1;
11 }
12 //0.取出服务器的ip和port
13 std::string sip=argv[1];
14 //stoi把字符串转int
15 uint16_t srvport = atoi(argv[2]);
16 //1.创建套接字
17 TCPSocket listen_sockfd;
18 CHECK_RET(listen_sockfd.Socket());
19 //2.绑定地址信息
20 CHECK_RET(listen_sockfd.Bind(sip,srvport));
21 //3.开始监听
22 CHECK_RET(listen_sockfd.Listen());
23 while(1){
24 //4.新建连接
25 //注意要放到循环里,否则只会接受一次请求
26 //开始写接受连接的三个输入参数
27 TCPSocket clisock;
28 std::string cliip;
29 uint16_t cliport;
30 bool ret = listen_sockfd.Accept(&clisock, &cliip,&cliport);
31 //注意要失败写continue,因为服务端可不能关闭
32 if (ret == false) {
33 continue;
34 }
35 //打印客户端的地址演示
36 std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
37
38
39 //5.收发数据
40 std::string buf;
41 ret = clisock.Recv(&buf);
42 if (ret == false) {
43 //注意走到这里套接字已经产生,注意销毁!!
44 clisock.Close();
45 continue;
46 }
47 std::cout << "client say: " << buf << std::endl;
48
49 buf.clear();//清空缓冲区
50 std::cout << "server say: ";
51 std::cin >> buf;
52 ret = clisock.Send(buf);
53 if (ret == false) {
54 clisock.Close();
55 }
56 }
57 //6.关闭监听套接字
58 listen_sockfd.Close();
59
60
61 }
1 #include "classtcp.hpp"
2
3 int main(int argc, char *argv[])
4 {
5 //通过参数传入要连接的服务器服务端的地址信息
6 if (argc != 3) {
7 printf("error\n");
8 return -1;
9 }
10 //存放需要访问的服务器的地址信息
11 std::string srvip = argv[1];
12 //字符串转数字
13 uint16_t srvport = std::stoi(argv[2]);
14
15 //建立监听套接字
16 TCPSocket cli_sock;
17 //1. 创建套接字
18 CHECK_RET(cli_sock.Socket());
19 //2. 绑定地址信息(不推荐)
20 //3. 向服务端发起连接
21 CHECK_RET(cli_sock.Connect(srvip, srvport));
22 while(1) {
23 //4. 收发数据
24 std::string buf;
25 std::cout << "client say: ";
26 std::cin >> buf;
27 CHECK_RET(cli_sock.Send(buf));
28
29 buf.clear();
30 CHECK_RET(cli_sock.Recv(&buf));
31 std::cout << "server say: " << buf << std::endl;
32 }
33 //5. 关闭套接字
34 CHECK_RET(cli_sock.Close());
35 return 0;
36 }
1 #include"classtcp.hpp"
2 #include<pthread.h>
3 #define CHECK_RET(q) if((q)==false){
return -1;}
4
5 //多线程版本就是把数据的收发分配给线程独立处理
6 //在线程的入口函数中,需要把处理客户端请求套接字转成指针发送过来,并且再转换成tcp套接字类型用指针处理函数。
7 //注意相比于普通版本,在入口函数退出时不能使用continue,需要把动态申请的空间释放,并退出(因为是单线程处理任务请求)
8 void *thr_entry(void *arg){
9 bool ret;
10 TCPSocket* clisock=(TCPSocket*)arg;
11 while(1){
12
13 std::string buf;
14 //5.收发数据
15 ret = clisock->Recv(&buf);
16 if (ret == false) {
17 //注意走到这里套接字已经产生,注意销毁!!
18 clisock->Close();
19 delete clisock;
20 return NULL;
21 //continue;
22 }
23 std::cout << "client say: " << buf << std::endl;
24
25 buf.clear();//清空缓冲区
26 std::cout << "server say: ";
27 std::cin >> buf;
28 ret = clisock->Send(buf);
29 if (ret == false) {
30 clisock->Close();
31 delete clisock;
32 return NULL;
33 }
34
35 }
36 clisock->Close();
37 delete clisock;
38 return NULL;
39 }
40
41 //我们用程序运行参数来直接绑定
42 //argv[0]就是这个程序的运行 argv[1]ip地址 argv[2]port号
43 int main(int argc,char *argv[]){
44 if (argc != 3) {
45 printf("error\n");
46 return -1;
47 }
48 //0.取出服务器的ip和port
49 std::string sip=argv[1];
50 //stoi把字符串转int
51 uint16_t srvport = atoi(argv[2]);
52 //1.创建套接字
53 TCPSocket listen_sockfd;
54 CHECK_RET(listen_sockfd.Socket());
55 //2.绑定地址信息
56 CHECK_RET(listen_sockfd.Bind(sip,srvport));
57 //3.开始监听
58 CHECK_RET(listen_sockfd.Listen());
59 while(1){
60 //4.新建连接
61 //注意要放到循环里,否则只会接受一次请求
62 //开始写接受连接的三个输入参数
63
64 //这里不一样(线程)
65 TCPSocket *clisock = new TCPSocket();
66 //TCPSocket clisock;
67 std::string cliip;
68 uint16_t cliport;
69 //没有新建连接就阻塞在这里
70 //变成指针这里要去掉取地址,因为我们要把这个套接字的指针传到函数里
71 bool ret = listen_sockfd.Accept(clisock, &cliip,&cliport);
72 //bool ret = listen_sockfd.Accept(&clisock, &cliip,&cliport);
73 //注意要失败写continue,因为服务端可不能关闭
74 if (ret == false) {
75 continue;
76 }
77 //打印客户端的地址演示
78 std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
79
80 //此时已经创建了新套接字,开始用线程交互
81 //从这里开始进入线程
82 pthread_t tid;
83 //pthread_create(&tid, NULL, thr_entry, &clisock);
84 //这里传递的是地址,随着while1的结束,栈变量会被释放
85 //所以我们要new一个新的套接字,堆空间不会被释放,因为需要手动释放
86 pthread_create(&tid, NULL, thr_entry, (void*)clisock);
87 pthread_detach(tid);
88 //clisock.Close();
89 //注意这个不能随意释放,因为全部线程共享,某一个关闭就都不能用了
90 //当某一个线程从入口函数出来不能释放!!
91
92
93 }//6要写到while1外面!!!!
94 //6.关闭监听套接字
95 listen_sockfd.Close();
96
97
98 }
1 #include"classtcp.hpp"
2 #include <signal.h>
3 #include <sys/wait.h>
4
W> 5 void sigcb(int no){
6 while(waitpid(-1,NULL,WNOHANG)>0);
7
8 }
9
10 void worker(TCPSocket &clisock){
11 bool ret;
12
13 while(1){
14
15 //5.收发数据
16 std::string buf;
17 ret = clisock.Recv(&buf);
18 if (ret == false) {
19 //注意走到这里套接字已经产生,注意销毁!!
20 clisock.Close();
21 exit(0);
22 //continue;
23 }
24 std::cout << "client say: " << buf << std::endl;
25
26 buf.clear();//清空缓冲区
27 std::cout << "server say: ";
28 std::cin >> buf;
29 ret = clisock.Send(buf);
30 if (ret == false) {
31 clisock.Close();//释放子进程里的clisock
32 exit(0);
33 }
34 }
35 clisock.Close();//释放子进程里的clisock
36 exit(0);
37 }
38
39 //我们用程序运行参数来直接绑定服务器的地址信息
40 //argv[0]就是这个程序的运行 argv[1]ip地址 argv[2]port号
41 int main(int argc,char *argv[]){
42 if (argc != 3) {
43 printf("error\n");
44 return -1;
45 }
46
47 //法1
48 //避免产生
49 //signal(SIGCHLD,SIG_IGN);
50 //忽略子进程退出的信号
51
52 //法2
53 //写一个毁掉函数
54 signal(SIGCHLD,sigcb);
55
56
57
58 //0.取出服务器的ip和port
59 std::string sip=argv[1];
60 //stoi把字符串转int
61 uint16_t srvport = atoi(argv[2]);
62 //1.创建套接字
63 TCPSocket listen_sockfd;
64 CHECK_RET(listen_sockfd.Socket());
65 //2.绑定地址信息
66 CHECK_RET(listen_sockfd.Bind(sip,srvport));
67 //3.开始监听
68 CHECK_RET(listen_sockfd.Listen());
69 while(1){
70 //4.新建连接
71 //注意要放到循环里,否则只会接受一次请求
72 //开始写接受连接的三个输入参数
73 TCPSocket clisock;
74 std::string cliip;
75 uint16_t cliport;
76 bool ret = listen_sockfd.Accept(&clisock, &cliip,&cliport);
77 //注意要失败写continue,因为服务端可不能关闭
78 if (ret == false) {
79 continue;
80 }
81 //打印客户端的地址演示
82 std::cout<<"get newconn:"<< cliip<<"-"<<cliport<<"\n";
83
84 //创建进程
85 pid_t pid=fork();
86 if(pid<0){
87 clisock.Close();
88 continue;//主进程继续去处理其他业务
89 }else if(pid==0){
90 //子进程
91 worker(clisock);
92 }
93 //释放父进程里的clisock,因为子复制父进程,子进程的clisock去工作了而父进程的没工作,每次循环都创建了。
94 clisock.Close();
95 }
96 // wait(NULL);//进程等待,但父进程不能卡在这里
97
98 //6.关闭监听套接字
99 listen_sockfd.Close();
100
101
102 }