TCP协议:传输控制协议——面向连接,可靠的字节流传输协议(确保数据安全有序到达对端)
TCP协议为了保证可靠传输,使用了很多处理机制来完成,所有它的传输性能相对于UDP较低。
TCP协议的应用场景:安全要求大于性能要求,比如文件传输、比如压缩包传输缺失一点点,或者乱序了压缩包就损坏了
UDP协议:用户数据报协议——无连接,不可靠的数据报传输协议(不确保数据安全有序到达对端)。
UDP协议不需要保证可靠传输,只需要管传输就行,因此传输性能要更高一些。
UDP协议的应用场景:性能要求大于安全要求,比如视频数据传输。视频传输20秒一花,总比一秒一卡来的强(而且视频传输如果丢失不是一个关键帧,视频连花屏都不会有,感受不到)
客户端要给服务端发送数据,客户端怎么知道服务端是谁?
服务端都会提前将自己的地址信息封装在客户端程序中。也正是因为如此,服务端的地址信息通常都不能随意改变。
网络传输中的数据都会具有五元组: sip, sport, dip, dport, protocol。五元组标识了一条通信:数据通哪来,到哪去,用的什么协议
客户端与服务端对于绑定地址的态度是不同的:客户端可绑,可不绑,不推荐绑;服务端是必须绑定。
创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
type:套接字类型——决定使用什么样的套接字传输方式,
SOCK_STREAM:流式套接字——基于连接的,有序的,可靠的字节流传输服务;TCP
SOCK_DGRAM:无连接的,不可靠的,数据报传输服务。UDP
protocol:使用协议类型——流式套接字默认0则表示TCP;数据报套接字默认0表示UDP。
在 vi /usr/include/netinet/in.h中查看协议
输入 /IPPROTO_TCP,IPPRTO_UDP
返回值:套接字操作句柄(文件描述符);失败返回-1
注意:
struct sockaddr是一个通用地址结构,在真正使用的时候并不用它(因为不同的通信有不同的地址结构)
通用——sockaddr
结构:
IPV4——sockaddr_in
结构:
IPV6——sockaddr_in6
UNIX——sockaddr_un
地址结构有很多种,但是绑定接口只有一个,因此使用通用类型来定义接口,真正使用的时候根据需求使用对应地址结构,然后强转类型传入数据即可,bind接口内会根据传入数据的前两个字节来决定这个传入的地址数据该如何解析。
网络通信需要网络字节序,因此需要考虑字节序转换的问题。
#include
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include <sys/socket.h>
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 #include<string.h>
8 int main(int argc ,char *argv[])
9 {
10
11 //从参数获取服务端要绑定的地址信息——IP地址与端口
12 if(argc < 3 )
13 {
14 printf("usage:./udp_srv 192.168.1.2 9000\n");
15 return -1;
16
17 }
18 char *srv_ip = argv[1];
19 int srv_port = atoi(argv[2]);
20
21 //1.创建套接字
22 //int socket(int domain, int type, int protocol)
23 int sockfd = socket(AF_INET ,SOCK_DGRAM, IPPROTO_UDP);
24 if(sockfd < 0 )
25 {
26 perror("socket error");
27 return -1;
28 }
29. //2 绑定地址信息
30 //int bind(int sockd, struct sockaddr *addr, socklen_t len);
31 struct sockaddr_in addr;
32 addr.sin_family = AF_INET;
33 addr.sin_port = htons(srv_port);
34
35 addr.sin_addr.s_addr = inet_addr(srv_ip);
36 socklen_t len = sizeof(addr);
37 int ret = bind(sockfd,(struct sockaddr*)&addr,len);
38
39 if(ret <0)
40 {
41
42 close(sockfd);
43 perror("bind error");
44 return -1;
45 }
46 while(1) {
47 //3.接收数据(接收数据的同时,还要接收对端地址信息)
48 //ssize_t recvfrom(int sockfd, void* data,int len,int flag,struct sockaddr* addr,socklen_t *alen)
49 char buf[4096]= {0};
50 struct sockaddr_in client_addr;
51 len = sizeof(client_addr);
52 ret = recvfrom(sockfd,buf,4095,0,(struct sockaddr*)&client_addr,&len);
53 if(ret < 0)
54 {
55 close(sockfd);
56 perror("recvfrom error");
57 return -1;
58 }
59
60 printf("%s:%d-%s\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf);
61 //4.发送数据
62 //ssize_t sendto(int sockfd,void *data,int len,int flag, structsockaddr* addr,socklen_t alen)
63 printf("serve input: ") ;
64 fflush(stdout);
65 memset(buf,0x00,4096);//情况buf缓冲区内容
66 scanf("%s",buf);
67 ret = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&client_addr,len);
68 if(ret < 0)
69 {
70 close(sockfd);
71 perror("sendto error");
72 return -1;
73 }
74
75 }
76 //5.关闭套接字
77 close(sockfd);
78
79 return 0;
80 }
vi udpsocket.hpp代码内容入下
1 #include<cstdio>
2 #include<iostream>
3 #include<string>
4 #include<arpa/inet.h>
5 #include<netinet/in.h>
6 #include<sys/socket.h>
7 #include<unistd.h>
8
9 class UdpSocket{
10 private:
11 int _sockfd; //贯穿了上下文,每-个操作都涉及到套接字描述符
12 public:
13 UdpSocket():_sockfd(-1){}
14 bool Socket()//创建套接字,其中的地址域类型这些都是固定
15 {
16 _sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
17 if(_sockfd <0)
18 {
19 perror("socket error");
20 return false;
21 }
22 return true;
23
24 }
25 bool Bind(const std::string &ip, int port){ //绑定地址信息
26 struct sockaddr_in addr;//定义IPv4地址结构,然后逐个赋值
27 addr.sin_family = AF_INET;//地址域类型
28 addr.sin_port = htons(port);//端口
29 addr.sin_addr.s_addr = inet_addr(ip.c_str());//将一个字符串的ip地址转换为网络字节序的ip地址
30 socklen_t len = sizeof(addr);//地址结构的长度
31 int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
32 if(ret <0)
33 {
34 perror("bind error");
35 return false;
36 }
37 return true;
38 }
39 bool Send(const std::string &data, const std::string &ip, int port){
40 //客户端实际上是不需要接收服务器地址的,因为它本身就知道
41 struct sockaddr_in peeraddr; //定义IPv4地址结构,然后逐个赋值
42 peeraddr.sin_family = AF_INET;//地址域类型
43 peeraddr.sin_port = htons(port);//端口
44 peeraddr.sin_addr.s_addr = inet_addr(ip.c_str());//将一个字符串的ip地址转换为网络字节序的ip地址
45 socklen_t len = sizeof(struct sockaddr_in);//地址结构的长度
46 int ret = sendto(_sockfd,&data[0],data.size(), 0,(struct sockaddr*)&peeraddr,len) ;
47 if(ret < 0)
48 {
49 perror("sendto error");
50 return false;
51 }
52 return true;
53
54
55
56
57 }
58 bool Recv(std::string *buf, std::string *ip = NULL, int *port = NULL){//接收数据
59 struct sockaddr_in peeraddr; //定义IPv4地址结构,然后逐个赋值
60 socklen_t len = sizeof(struct sockaddr_in);//地址结构的长度
61 char tmp[4096] = {0};
62 int ret = recvfrom(_sockfd, tmp, 4095, 0, (struct sockaddr*)&peeraddr, &len);
63 if(ret < 0)
64 {
65 perror("recv error");
66 return false;
67 }
68 //并没有使用赋值操作,因为赋值是字符串操作,遇到反斜杠0就停止了,但是网络传输,什么数据都有可能,因为需要不管什么数据> ,只管截取指定长度
69 buf->assign(tmp, ret);//从tmp字符串开始截取ret长度的数据到buf中—>给buf申请一个长度为ret的空间。
70 if(ip!=NULL) *ip = inet_ntoa(peeraddr.sin_addr);
71 if(port != NULL) *port = ntohs(peeraddr.sin_port);
72 return true;
73
74 }
75 bool Close(){
76 if(_sockfd >= 0)
77 {
78 close(_sockfd);
79 }
80 return true;
81 }
82 //
83 };
vi udp_client.cpp 代码内容如下:
1 #include"udpsocket.hpp"
2 #include<string>
3 #include<stdlib.h>
4 #include<cstdlib>
5 using namespace std;
6 #include<unistd.h>
7
8 #define CHECK_RET(q) if((q)==false) {return -1;}
9
10
11 int main(int argc,char*argv[])
12 {
13 //通过运行参数指定服务端地址信息
14 if(argc < 3){
15 cout << "usage:请输入服务端地址信息\n!" ;
16 cout << "\t ./udp_cli 192.168.1.2 9000\n";
17 return -1;
18 }
19 string srv_ip = argv[1];
20 int srv_port = atoi(argv[2]);//stoi 字符串转整形
21 //1.创建套接字
22 UdpSocket sock;
23 CHECK_RET( sock.Socket());
24
25
26
27 //2.绑定地址信息(不推荐绑定)
28
29
30 //3.发送请求
31 while(1)
32 {
33 string buf;
34 cout<<"client input:";
35 cin>>buf;//从标准输入获取数据
36 CHECK_RET(sock.Send(buf,srv_ip, srv_port));
37
38 //4.接收响应
39 buf.clear();
40 CHECK_RET(sock.Recv(&buf));
41 cout << "server respomse: " << buf << endl;
42 }
43 //5.关闭套接字
44 sock.Close();
45
46
47 return 0;
48 }
1 udp_cli:udp_client.cpp
2 g++ $^ -o $@
3 udp_srv:udp_srv.c
4 gcc $^ -o $@
先 ifconfig查看当前ip地址
然后输入命令 : ./udp_srv 192.168.38.148 9000
此处的ip就是我们刚刚上一步查询到的ip 9000是自己设置的端口号。
tcp通信程序的编写:基于连接的,可靠的字节流传输协议。
与udp不同的是,udp是无连接的,只要知道对方的地址就可以给对方发送数据(当然它不管数据能不能达到对方)
而tcp不同,tcp是基于连接,首先需要建立连接成功,之后才能进行数据传输。
int socket (int domain, int type, int protocol)
返回值:成功返回-个非负整数-操作句柄- -套接字描述符;失败返回-1
int bind(int sockfd, struct sockaddr *addr, socklen_ t len);
返回值:成功返回0;失败返回-1;
int listen(int sockfd, int backlog);
返回值:成功返回0;失败返回-1;
int connect(int sockfd, struct sockaddr *addr, socklen t len);
返回值:成功返回0;失败返回-1
int accept(int sockfd, struct sockaddr *addr,socklen_ t *len)
返回值:新建连接的套接字描述符——操作句柄——用于后续与客户端进行通信;失败返回-1;
ssize_t send(int sockfd, void *data, int len,int flag)
返回值:成功返回实际发送的数据长度;失败返回-1
ssize_ t recv(int sockfd, void *buf, int len, int flag)
返回值:成功返回实际获取的数据长度;失败返回-1;连接断开返回0( 当服务端recv返回0时,确实没有接收到数据,但是更多情况是为了表示连接断开)
int close(int fd);
int shutdown(int sockfd, int how);
注意: 它并没有完全释放资源。shutdown更多用于进行半关闭连接,让对方知道自己不再发送数据或者不再接收数据了,但是要注意shutdown不是用于关闭套接释放资源的,用了shutdown之后还是要使用close关闭套接字释放资源的。
class TcpSocket{
private:
int_ sockfd;
public:
TcpSocket():_ sockfd(-1){}
bool Socket();//创建套接字
bool Bind(const std:string &ip, int port);//绑定地址信息
bool Listen(int backlog = MAX_ LISTEN);//服务端开始监听
bool Connect(const std::string &srv_ ip, int srv _port);//向服务端发起连接请求
bool Accept(TcpSocket *new_ sock, std::string *cli_ ip, int *cli _port);//获取新建连接
bool Send(const std:string &data);
bool Recv(std:string *buf);
bool Close();
};
vi tcpsocket.hpp
1 #include<iostream>
2 #include<unistd.h>
3 #include<arpa/inet.h>
4 #include<netinet/in.h>
5 #include<sys/socket.h>
6 #include<string>
7 using namespace std;
8 #define CHECK_RET(q) if((q) == false){return -1;}//一个校验
9 #define MAX_LISTEN 5
10 class TcpSocket{
11 private:
12 int _sockfd;
13 public:
14 TcpSocket():_sockfd(-1){}
15 bool Socket()//创建套接字
16 {//int socket(int domain, int type, int protocol)
17 _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
18 if (_sockfd < 0)
19 {
20 perror("socket error ");
21 return false;
22 }
23
24 return true;
25
26 }
27 bool Bind(const std::string &ip, int port)//绑定地址信息
28 {
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 socklen_t len = sizeof(addr);
35 if( bind(_sockfd,(struct sockaddr*)&addr,len ) < 0)
36 {
37 perror("bind error");
38 return false;
39 }
40 return true;
41
42 }
43 bool Listen(int backlog = MAX_LISTEN)//服务端开始监听
44 {//int listen(int sockfd, int backlog);
45 if(listen(_sockfd,backlog) < 0)
46 {
47 perror("listen error");
48 return false;
49 }
50 return true;
51 }
52 bool Connect(const std::string &srv_ip, int srv_port)//向服务端发起连接请求
53 {//int connect(int sockfd, struct sockaddr *addr, socklen t len);
54 struct sockaddr_in addr;
55 addr.sin_family = AF_INET;
56 addr.sin_port = htons(srv_port);
57 addr.sin_addr.s_addr = inet_addr(srv_ip.c_str()) ;
58 socklen_t len =sizeof(addr);
59
60 if(connect(_sockfd,(struct sockaddr*)&addr,len) < 0)
61 {
62 perror("connect error");
63 return false;
64 }
65 return true;
66 }
67 bool Accept(TcpSocket *new_sock, std::string *cli_ip, int *cli_port)//获取新建连接
68 {//int accept(int sockfd, struct sockaddr *addr,socklen_ t *len)
69 struct sockaddr_in addr;
70 socklen_t len = sizeof(addr);
71 int new_fd = accept(_sockfd,(struct sockaddr *)&addr,&len);
72
73 if(new_fd < 0)
74 {
75 perror("accept error");
76 return false;
77 }
78 new_sock->_sockfd = new_fd;
79 if(cli_ip != NULL) *cli_ip = inet_ntoa(addr.sin_addr);
80 if(cli_port != NULL) *cli_port = ntohs(addr.sin_port);
81 return true ;
82 }
83 bool Send(const std::string &data)
84 {//ssize_t send(int sockfd, void *data, int len,int flag)
85 ssize_t ret = send(_sockfd,data.c_str(),data.size(),0);
86 if(ret < 0)
87 {
88 perror ("send error");
89 return false;
90 }
91 return true;
92 }
93 bool Recv(std::string *buf)
94 {
95 // ssize_ t recv(int sockfd, void *buf, int len, int flag)
96 char tmp[4096] = {0};
97 ssize_t ret = recv(_sockfd,tmp,4096,0);
98 if(ret < 0)
99 {
100 perror("recv error");
101 return false;
102 }else if(ret == 0)
103 {
104 cout << "connetc shutdown!\n";
105 return false;
106 }
107 buf->assign(tmp,ret);
108 return true;
109 }
110 bool Close()
111 {
112 if(_sockfd > 0)
113 {
114 close(_sockfd);
115 _sockfd = -1;
116 return false;
117 }
118 return true;
119 } 120 };
vi tcp_cli.cpp
1 #include"tcpsocket.hpp"
2 #include<string>
3 using namespace std;
4
5
6 int main(int argc,char*argv[])
7 {
8
9 //客户端虽然不用主动绑定地址信息,但是必须知道服务端地址
10 if(argc < 3)
11 {
12 cout<< "usage: arg error \n";
13 cout <<"\t ./tcp.cli 192.168.124.2 9000\n";
14 return -1;
15 }
16 int srv_port = stoi(argv[2]);
17 string srv_ip = argv[1];
18
19 //搭建客户端
20 //实例化一个对象
21 TcpSocket sock;
22 //1.创建套接字
23 CHECK_RET(sock.Socket());
24 //2.向服务端发起连接请求
25 CHECK_RET(sock.Connect(srv_ip,srv_port));
26 //3.循环发起数据
27 while(1)
28 {
29 string data;
30 cout << "client input:";
31 fflush(stdout);
32 cin >> data;
33 CHECK_RET(sock.Send(data));
34
35 data.clear();
36 CHECK_RET(sock.Recv(&data));
37 cout << "server response" << data << endl;
38
39
40 }
41 //关闭套接字
42 sock.Close();
43 return 0;
44 }
make 一下
#include
#include "tcpsocket.hpp"
int main(int argc, char *argv[])
{
if (argc < 2) {
std::cout << "usage: ./tcp_srv 9000\n";
return -1;
}
int port = std::stoi(argv[1]);
TcpSocket lst_sock;
//创建套接字
CHECK_RET(lst_sock.Socket());
//绑定地址信息, "0.0.0.0"会被识别为本机上任意网卡IP地址--绑定0.0.0.0就表示绑定了本机上所有网卡
CHECK_RET(lst_sock.Bind("0.0.0.0", port));
//开始监听
CHECK_RET(lst_sock.Listen());
while(1) {
//获取新建连接,获取了与哪个客户端新建的连接,则与哪个客户端进行通信
TcpSocket new_sock;
std::string cli_ip;
int cli_port;
CHECK_RET(lst_sock.Accept(&new_sock, &cli_ip, &cli_port));
std::cout << "new connect: " << cli_ip << ":" << cli_port << "\n";
//使用新建连接收发数据, 一定要注意,通信使用的是新获取的套接字,而不是监听套接字
std::string buf;
new_sock.Recv(&buf);//接收数据
struct cal_t *cal = (struct cal_t *)&buf[0];//获取空间首地址
for (int i = 0; i < sizeof(struct cal_t); i++) {
printf("%02x", buf[i]);
}
printf("\n");
std::cout << cal->num1 << cal->op << cal->num2 << std::endl;
int sum = 0;
if (cal->op == '+') {
sum = cal->num1 + cal->num2;
}
new_sock.Send(std::to_string(sum));//to_string将数字转string的接口
}
//关闭套接字
lst_sock.Close();
return 0;
}
make一下