预备知识
- 套接字编程:网络通信程序的编写
- 网络中的通信都是两端主机之间的通信
- 举例:QQ聊天
qq聊天,虽然看起来是想跟谁聊就跟谁聊,包括群聊,其实你并不是跟另一个手机或者电脑用户在通信,你实际上是跟腾讯的服务器在进行通信,把一个消息发到某一个群里,其实是把数据发送给了服务器,描述数据在哪个群,服务器就能找到群里有哪些用户,这些用户也登录了服务器,就可以把数据逐一发送给对应的主机。
- 微信可以分享给qq文件:
分享这个操作,其实是打开了qq的程序,其实还是qq与qq、微信与微信的通信。 - 网络中的各种通信,都是服务器与服务器之间的通信,不存在两个用户之前的通信。
- 网络之间 的通信都是两端主机之间的通信:客户端,服务端
客户端:网络通信中用户的一端,是进行业务请求的一端,是主动发起请求的一端。
服务端:网络通信中提供服务的一端,针对客户端请求进行处理的一端,是被动接收请求的一端。
网络通信编程
- 应用程序的编写是程序员自己完成,因此应用层用什么协议,格式如何约定由程序员自己确定。但是应用层往下都是操作系统完成的工作,而传输层有两个典型协议:TCP、UDP
- 客户端要给服务端发送数据——如何知道服务端是谁? 服务端会提前把自己的地址信息封装在客户端程序中。
也正是因为如此,服务端的地址信息通常都不能随意改变。 - 网络传输中的数据通常都是有五元组:sip,sport,dip,dport,protocol。五元组标识了一条通信:数据从哪来,到哪去,用的什么协议
1 初识TCP和UDP协议的区别
1.1 TCP协议
TCP协议:传输控制协议——面向连接,可靠的字节流传输协议(确保数据安全有序到达对端)
TCP协议为了保证可靠传输,使用了很多处理机制来完成,所有它的传输性能相对于UDP较低。
TCP协议的应用场景:安全要求大于性能要求,比如文件传输、比如压缩包传输缺失一点点,或者乱序了压缩包就损坏了
1.2 UDP协议
UDP协议:用户数据报协议——无连接,不可靠的数据报传输协议(不确保数据安全有序到达对端)。
UDP协议不需要保证可靠传输,只需要管传输就行,因此传输性能要更高一些。
UDP协议的应用场景:性能要求大于安全要求,比如视频数据传输。视频传输20秒一花,总比一秒一卡来的强(而且视频传输如果丢失不是一个关键帧,视频连花屏都不会有,感受不到)
2 UDP协议通信原理
客户端要给服务端发送数据,客户端怎么知道服务端是谁?
服务端都会提前将自己的地址信息封装在客户端程序中。也正是因为如此,服务端的地址信息通常都不能随意改变。
网络传输中的数据都会具有五元组: sip, sport, dip, dport, protocol。五元组标识了一条通信:数据通哪来,到哪去,用的什么协议
1.UDP通信流程
- 注意:
客户端第二步:通常都不绑定自己的地址,UDP通信的数据在发送缓冲区是不会进行等待的,会直接封装头部信息进行发送。发送的时候网卡发现它没有源端地址,会自动分配一个合适的源端端口。 - 例子:
我把我的电话给了小姐妹,说有考试成绩出来了打电话给我,小姐妹到时候打电话过来,用谁的电话都行,其实我也不需要知道这个打过来的电话号码是多少,只要能够完成这次通信就可以。 - 因此客户端这边的地址不需要固定必须使用哪个地址。客户端程序其实也不需要知道自己绑定的是什么地址。
- 但是服务端不同,服务端因为提前告知了对方自己的地址信息,因此就必须在某个固定的地址上接收数据(否则一旦改变,客户端就找不到自己了);
客户端与服务端对于绑定地址的态度是不同的:客户端可绑,可不绑,不推荐绑;服务端是必须绑定。
2.接口认识
2.1 创建套接字
-
创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol); -
domain:地址域类型——用于决定通信使用什么样的地址结构,IPV地址域类型则是 AF_INET
-
type:套接字类型——决定使用什么样的套接字传输方式,
SOCK_STREAM:流式套接字——基于连接的,有序的,可靠的字节流传输服务;TCP
SOCK_DGRAM:无连接的,不可靠的,数据报传输服务。UDP
-
protocol:使用协议类型——流式套接字默认0则表示TCP;数据报套接字默认0表示UDP。
在 vi /usr/include/netinet/in.h中查看协议
输入 /IPPROTO_TCP,IPPRTO_UDP
-
返回值:套接字操作句柄(文件描述符);失败返回-1
2.2 绑定端口号 (地址信息)
- 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, struct sockaddr *addr,socklen_t address_len); - sockfd:创建套接字返回的操作句柄——用于决定给哪个套接字绑定地址
- addr:绑定的地址信息
- address_len:地址信息长度
注意:
struct sockaddr是一个通用地址结构,在真正使用的时候并不用它(因为不同的通信有不同的地址结构)
通用——sockaddr
结构:
IPV4——sockaddr_in
结构:
IPV6——sockaddr_in6
UNIX——sockaddr_un
地址结构有很多种,但是绑定接口只有一个,因此使用通用类型来定义接口,真正使用的时候根据需求使用对应地址结构,然后强转类型传入数据即可,bind接口内会根据传入数据的前两个字节来决定这个传入的地址数据该如何解析。
- address_len:地址信息长度——因为第二个参数传入的是地址,这里要指定长度,防止访问越界
- 返回值:成功返回 0 ;失败返回-1
eg:
2.3 发送数据
- 发送数据
ssize_ t sendto(int sockfd, void *buf, int len,int flag,struct sockaddr *peer_ addr, socklen t addrlen) - sockfd:创建套接字返回的操作句柄;
- buf:要发送的数据首地址(socket并不关心发送的是什么数据)
- len:要发送的数据长度
- flag:标志位——0默认阻塞操作
- peer_ addr:目的端地址信息
- addrlen:地址信息长度
- 返回值:成功返回实际发送的数据长度;失败返回-1;
2.4 接收数据
- 接收请求 (TCP, 服务器)
ssize_ t recvfrom(int sockfd, void * buf, int len, int flag, struct sockaddr * peeraddr, socklen_t *addrlen) - sockfd:创建套接字返回的操作句柄;
- buf:一块缓冲区空间首地址,用于存放获取的数据
- len:想要获取的数据长度
- flag:标志位——0默认阻塞操作(若缓冲区没有数据则会等待)
- peer_ addr:接收数据的源端地址信息
- *addrlen:输入输出参数用于指定要获取的地址长度以及返回实际长度。
- 返回值:成功返回实际获取到的数据长度;失败返回-1;
2.5 关闭套接字
- int close(int fd);
2.6 字节序转换接口
网络通信需要网络字节序,因此需要考虑字节序转换的问题。
- 16位数据的主机与网络字节序转换:
unit16_t htons(uint16_t port)
unit16_t ntohs(uint16_t port)
h:host n:net s:short - 32位数据的主机与网络字节序转换:
unit32_t htonl(uint32_t ip)
unit32_t ntohl(uint32_t ip)
l:long - 我们平常见到的IP地址:192.168.1.1——点分十进制的字符串IP地址(下面这两个接口只能转换IPV4的地址信息)
- in_addr_t inet_addr(const char *ip):将一个点分十进制的字符串IP地址转换为网络字节序整数IP地址。
- const char * inet_ntoa(struct in_addr addr sin_addr):将一个网络字节序整数IP地址转换为点分十进制的字符串IP地址。
- inet_ntop/inet_pton:不仅仅可以转换IPV4的地址信息,还可以转换其他的地址结构
注意: - 端口数据的转换不能使用htonl/ntohl
3. 接口实现
- 首先使用C语言编写UDP服务端程序——了解接口的实际使用与细节流程。
- 然后使用C++封装一个UdpSocket类,通过实例化对象能够简单完成客户端的搭建。
3.1 创建服务端 udp_srv
- 创建一个socket目录、 进入sockert目录 vi udp_srv.c
man socket 查看我们要引入的头文件
- 查看网络连接状态的命令: netstat
-a查看所有信息
-n不以服务名称显示(22端口被 显示成为ssh,127.0.0.1被 显示为localhost)
-p查看对应网络状态信息所属的进程
-t tcp连接信息;
-u udp连接信息
- 绑定地址信息(服务端绑定的地址一定是本机的ip地址)
输入命令: gcc udp_srv.c -o udp_srv
运行程序:./udp_srv 192.168.38.148 9000
sudo netstat -anpu
编写udp_srv.c 内代码如下:
#include<stdio.h>
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 }
3.2 封装一个udpsocket类
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 };
3.3 创建客户端 udp_client
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 }
3.4 编译
- makefile文件
1 udp_cli:udp_client.cpp
2 g++ $^ -o $@
3 udp_srv:udp_srv.c
4 gcc $^ -o $@
- make 一下
3.5 通信
-
复制两个ssh渠道。一个作为客户端,一个作为服务器端
-
确定服务端
先 ifconfig查看当前ip地址
然后输入命令 : ./udp_srv 192.168.38.148 9000
此处的ip就是我们刚刚上一步查询到的ip 9000是自己设置的端口号。
- 确定客户端
打开我们之前复制的第二个ssh通道,输入: ./udp_cli
它会提示我们输入服务端地址信息,我们此时需要输入服务端的ip地址: ./udp_cli 192.168.168.148 9000
- 进行通信
从客户端发起请求:输入:nihao
我们打开服务器端可以收到客户端的消息
然后我们服务端进行回复:lihaoya
我们的客户端就能收到服务端的消息
3 TCP协议通信程序的编写
1.TCP通信流程
tcp通信程序的编写:基于连接的,可靠的字节流传输协议。
与udp不同的是,udp是无连接的,只要知道对方的地址就可以给对方发送数据(当然它不管数据能不能达到对方)
而tcp不同,tcp是基于连接,首先需要建立连接成功,之后才能进行数据传输。
2.接口认识
2.1 创建套接字
int socket (int domain, int type, int protocol)
- domain:地址域类型- AF_ INET-表示IPV4版本通信
- type:套接字类型-SOCK_ STREAM-表示流式套接字
- protocol:协议类型-0默认在SOCK_ STREAM下表示tcp协议,IPPROTO _TCP
返回值:成功返回-个非负整数-操作句柄- -套接字描述符;失败返回-1
2.2 绑定地址信息
int bind(int sockfd, struct sockaddr *addr, socklen_ t len);
- sockfd:套接字描述符创建套接字返回的操作句柄
- addr:要绑定的地址信息,ipv4通信应该使用struct sockaddr in 结构
- len:地址信息长度
返回值:成功返回0;失败返回-1;
2.3 服务端开始监听
int listen(int sockfd, int backlog);
- sockfd:套接字描述符
- backlog:当前服务端在同- -时间所能处理的最大的客户端连接请求数量(同一时刻的最大并发连接数)
- SYN泛洪攻击:
有人恶意主机伪造IP地址,向服务器发送大量连接建立请求,这样服务端就会不断的创建大量的通信套接字,如果服务端对创建的新建套接字数量不做限制,有可能瞬间资源耗尽,系统崩溃。
基于这种情况,为避免系统崩溃,因此服务端这边对同一时刻所能创建的新的套接字的数量做了最大限制,而这个限制的数量就是listen的第二个参数,有了这个限制之后,遇到了syn泛洪攻击,顶多无法处理正常请求,但是不会让系统崩溃,之前的连接都还可以正常通信。
返回值:成功返回0;失败返回-1;
2.4 客户端发送连接请求
int connect(int sockfd, struct sockaddr *addr, socklen t len);
- sockfd:套接字描述符
- addr:服务端的地址信息,ipv4通信使用struct sockaddr_ in结构.
- len:地址信息长度;
返回值:成功返回0;失败返回-1
2.5 服务端获取新建连接句柄
int accept(int sockfd, struct sockaddr *addr,socklen_ t *len)
- sockfd:监听套接字描述符(监听套接字仅用于监听以及获取新建连接)
- addr:客户端地址信息 (描述当前获取的新建连接句柄是与哪个客户端通信的)
- len:输入输出型参数,指定要获取的地址长度,以及返回实际获取的地址长度
返回值:新建连接的套接字描述符——操作句柄——用于后续与客户端进行通信;失败返回-1;
2.6 发送数据
ssize_t send(int sockfd, void *data, int len,int flag)
- sockfd:套接字描述符(对于服务端来说,是accept获取到的新建连接的描述符)
- data:要发送的数据首地址
- len: 要发送的数据长度
- flag: 标志位-通常置0,表示阻塞发送(发送数据就是把数据放到发送缓冲区,系统进行封装发送,放不进去则等待)
返回值:成功返回实际发送的数据长度;失败返回-1
2.7 接收数据
ssize_ t recv(int sockfd, void *buf, int len, int flag)
- sockfd:套接字描述符
- buf:一 个缓冲区空间首地址,用于放置接收的数据
- len: 想要获取的数据长度,但是不能大于buf的缓冲区空间大小
- flag: 标志位通常置0,表示阻塞接收(socket接收缓冲区中没有数据则阻塞)
返回值:成功返回实际获取的数据长度;失败返回-1;连接断开返回0( 当服务端recv返回0时,确实没有接收到数据,但是更多情况是为了表示连接断开)
2.8 关闭套接字
int close(int fd);
2.9 关闭部分连接
int shutdown(int sockfd, int how);
- sockfd:套接字描述符
- how:要关闭的操作类型—— SHUT_ RD、SHUT _WR、SHUT RDWR
注意: 它并没有完全释放资源。shutdown更多用于进行半关闭连接,让对方知道自己不再发送数据或者不再接收数据了,但是要注意shutdown不是用于关闭套接释放资源的,用了shutdown之后还是要使用close关闭套接字释放资源的。
3 接口实现
- 封装一个TcpSocket类,通过这个类的成员接口完成客户端与服务端的搭建
- 使用TcpSocket类实例化的对象搭建一个tcp客户端
- 使用TcpSocket类实例化的对象搭建一个tcp服务端
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();
};
3.1 封装一个Tcpsocket 类
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 };
3.2 搭建一个tcp客户端
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 一下
3.3 搭建一个tcp服务端
#include <unordered_map>
#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一下