1.网络通信的数据中都会包含一个完整的五元组:sipsportdipdporprotocol(源端IP,源端端口,对端IP,对端端口,协议五元组完整的描述了数据从哪来,到哪去,用的是什么数据格式。
2.网络通信,通常讨论的是两个主机进程之间的通信:客户端&服务端
客户端网络通信程序:通常指的是用户使用的一端
服务端网络通信程序:通常指的是网络应用提供商提供服务的一端程序
我们后边说找工作找的后台服务器开发工作,指的是编写服务端程序的工作。
并且我们要了解:客户端永远都是首先发起请求的一端(因为服务端是不知道客户端地址的(动态地址分配技术--谁上网给谁分配地址))
但是网络应用服务提供商,开发的客户端程序中都写入了服务器端的地址和端口,因此客户端是知道服务端地址的。还有一种原因就是,只有客户发送了请求,服务端才能提供对应的服务
套接字编程:
套接字:socket的翻译,通常表示的是系统提供给程序员实现网络通信的一套接口.
tcp协议:传输控制协议--提供的是面向连接,可靠,基于字节流的数据传输
面向连接:通信前先要确定双方是否具有数据收发的能力
可靠传输:通过大量的一些控制机制,保证数据能够安全(有序且完整,一致)到达对端。
udp协议:用户数据报协议--提供的是无连接,不可靠,基于数据包的数据传输
无连接:需要建立连接,只要知道对方的地址,就可以直接发送数据
不可靠:只要数据发送出去了就行,不管是否能够到达对端
数据报:有最大大小限制,且传输交付有大小限制的一种传输方式
因为没有大量的控制机制,因此传输速度快,因此适用于实时性要求大于安全性要求的场景,比如视频传输,音频传输。
接口:
//1、创建套接字:
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
domin:地址域类型(域间通信,ipv4通信,ipv6通信.。。不同通信方式有不同的地址结构);AF_INET--IPV4地址域类型
type: 套接字类型
SOCK_STREAM:流式套接字,提供的是字节流传输,默认协议是TCP协议
SOCK_DGRAM:数据报套接字,提供的是数据报传输,默认协议是UDRP协议
protocol;协议类型
IPPROTO_TCP:值为6
IPPROTO_UDP:值为17
返回一个套接字描述符;失败返回-1;
//2、为套接字绑定地址信息
// 绑定端口号,地址信息 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听socket (TCP, 服务器)
//socket:socket返回的套接字描述符
//address 要绑定的地址信息(不同的地址域类型,有不同的地址结构)
成功返回0,失败返回-1;
//3、发送数据
ssize_t sendto(int sockfd,void*buf,size_t len,int flag, struct sockaddr *peer,socklen_t len)
/3、接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *peer, socklen_t *addrlen);
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
5.关闭套接字,释放资源
int close(int fd);
1 #include<iostream>
2 #include<string.h>
3 #include<arpa/inet.h>
4 #include<netinet/in.h>
5 #include<sys/socket.h>
6 #include<unistd.h>
7 #include<stdio.h>
8 class udpsocket{
9 private:
10 int _socket;
11 public:
12 udpsocket():_socket(-1){}
13 ~udpsocket(){
14 Close();
15 }
16 public:
17 bool Socket()//创建套接字
18 {
19 _socket=socket(AF_INET,SOCK_DGRAM ,IPPROTO_UDP);
20 if(_socket<0){
21 perror("socket error");
22 return false;
23 }
24 return true;
25 }
26 bool Bind(const std::string &ip,uint16_t port){
27 struct sockaddr_in addr;
28 addr.sin_family=AF_INET;
29 addr.sin_port=htons(port);
30 addr.sin_addr.s_addr=inet_addr(ip.c_str());
31 socklen_t len=sizeof(struct sockaddr_in);
32 int ret =bind(_socket,(struct sockaddr*)&addr,len);
33 if(ret<0){
34 perror("bind error");
35 return false;
36 }
37 return true;
38 }
39 bool Recv(std::string *body,std::string *peer_ip=NULL,uint16_t *peer_port=NULL){
40 struct sockaddr_in peer;
41 char tmp[4096]={0};
42 socklen_t len=sizeof(struct sockaddr_in);
43 ssize_t ret=recvfrom(_socket,tmp,4096,0,(struct sockaddr*)&peer,&len);
44 if(ret<0){
45 perror("recvfrom error");
46 return false;
47 }
48 if(peer_ip!=NULL) *peer_ip=inet_ntoa(peer.sin_addr);
49 if(peer_port!=NULL) *peer_port=ntohs(peer.sin_port);
50 body->assign(tmp,ret);
51 return true;
52 }
53 bool Send(const std::string &body,const std::string &peer_ip,uint16_t peer_port){
54 struct sockaddr_in addr;
55 addr.sin_family=AF_INET;
56 addr.sin_port=htons(peer_port);
57 addr.sin_addr.s_addr=inet_addr(peer_ip.c_str());
58 socklen_t len=sizeof(struct sockaddr_in);
59 ssize_t ret=sendto(_socket,body.c_str(),body.size(),0,(struct
sockaddr*)&addr,len);
60 if(ret<0){
61 perror("sendto error");
62 return false;
63 }
64 return true;
65 }
66 bool Close(){
67 if(_socket!=-1){
68 close(_socket);
69 _socket=-1;
70 }
71 return true;
72 }
73 };
1 #include"udp_socket.hpp"
2 #include<assert.h>
3 int main(int argc,char *argv[])
4 {
5 if(argc!=3){
6 std::cout<<"usage: ./udp_cli 192.168.2.2 9000\n";
7 return -1;
8 }
9 std::string srv_ip=argv[1];
10 uint16_t srv_port=std::stoi(argv[2]);
11 udpsocket cli_sock;
12 //1、创建套接字
13 assert(cli_sock.Socket()==true);
14 //2、为套接字绑定地址信息
15 //循环通信
16 while(1){
17 //发送数据
18 std::string data;
19 std::cout<<"client say: ";
20 fflush(stdout);
21 std::cin>>data;
22 assert(cli_sock.Send(data,srv_ip,srv_port)==true);
23 //接收数据
24 data.clear();
25 assert(cli_sock.Recv(&data)==true);
26 std::cout<<"serve say:"<<data<<std::endl;
27 }
28 //5、关闭套接字
29 cli_sock.Close();
30
31 return 0;
32 }
TCP协议:
接口:
关闭套接字,释放资源
int close(int fd);
代码示例:
//封装库
#include<iostream>
2 #include<string>
3 #include<cassert>
4 #include<arpa/inet.h>
5 #include<unistd.h>
6 #include<netinet/in.h>
7 #include<sys/socket.h>
8 #include<stdio.h>
9 int MAX_LISTEN=1024;
10 class TcpSocket{
11 private:
12 int _sockfd;
13 public:
14 TcpSocket(): _sockfd(-1){};
15 ~TcpSocket(){
16 // Close();
17 }
18 int fd(){
19 return _sockfd;
20 }
21 void set(int fd){
22 _sockfd=fd;
23 }
24 bool Socket(){
25 _sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
26 if(_sockfd<0){
27 perror("sockfd error");
28 return false;
29 }
30 return true;
31 }
32 bool Bind(const std::string &ip,uint16_t port){
33 struct sockaddr_in addr;
34 addr.sin_family=AF_INET;
35 addr.sin_port=htons(port);
36 addr.sin_addr.s_addr=inet_addr(ip.c_str());
37 socklen_t len=sizeof(sockaddr_in);
38 int ret=bind(_sockfd,( struct sockaddr*)&addr,len);
39 if(ret<0){
40 perror("bind error");
41 return false;
42 }
43 return true;
44 }
45 bool Listen(int backlog=MAX_LISTEN)
46 {
47 int ret=listen(_sockfd,backlog);
48 if(ret<0){
49 perror("listen error");
50 return false;
51 }
52 return true;
53 }
54 bool Connect(const std::string &srvip,uint16_t srvport){
55 struct sockaddr_in addr;
56 addr.sin_family=AF_INET;
57 addr.sin_port=htons(srvport);
58 addr.sin_addr.s_addr=inet_addr(srvip.c_str());
59 socklen_t len=sizeof(sockaddr_in);
60 int ret=connect(_sockfd,(struct sockaddr*)&addr,len);
61 if(ret<0){
62 perror("connect error");
63 return false;
64 }
65 return true;
66
67 }
68
69 bool Accept(TcpSocket *new_sock,std::string *cli_ip=NULL,uint16_t *cli_port=NULL){
70 struct sockaddr_in peer;
71 socklen_t addrlen=sizeof(struct sockaddr_in);
72 int newfd=accept(_sockfd,(struct sockaddr*)&peer,&addrlen);
73 if(newfd<0){
74 perror("accept error");
75 return false;
76 }
77 new_sock->_sockfd=newfd;
78
79 if(cli_ip) *cli_ip=inet_ntoa(peer.sin_addr);
80 if(cli_port) *cli_port=ntohs(peer.sin_port);
81 return true;
82 }
83
84 bool Send(const std::string&body){
85 ssize_t ret=send(_sockfd,body.c_str(),body.size(),0);
86 if(ret<0){
87 perror("send error");
88 return false;
89 }
90 return true;
91 }
92 bool Recv(std::string *body)
93 {
94 //接收数据
95 char temp[1024]={0};
96 ssize_t ret=recv(_sockfd,temp,1023,0);
97 if(ret<0){
98 perror("Recv error");
99 return false;
100 }
101 else if(ret==0){
102 perror("connect break");
103 return false;
104 }
105 body->assign(temp,ret);
106 return true;
107 }
108 bool Close(){
109 if(_sockfd!=-1){
110 close(_sockfd);
111 _sockfd=-1;
112 }
113 return true;
114 }
115
116 };
//服务端
#include"tcp_socket.hpp"
int main(int agc,char *argv[])
{
if(agc!=3){
printf("usage: ./tcp_srv 192.168.177.128 9000");
return -1;
}
std::string ip=argv[1];
uint16_t port=std::stoi(argv[2]);
//服务器流程
TcpSocket lsn_sock;
//创建套接字
assert( lsn_sock.Socket()!=false);
//为套接字绑定地址信息
assert(lsn_sock.Bind(ip,port));
//开始监听
assert(lsn_sock.Listen());
//获取新建连接
while(1){
TcpSocket new_sock;
std::string cli_ip;
uint16_t cli_port;
bool ret=lsn_sock.Accept(&new_sock,&cli_ip,&cli_port);
if(ret==false){
continue;
}
std::cout<<"new client:"<<cli_ip<<":"<<cli_ip<<std::endl;
//使用新建连接发送数据
std::string buf;
ret=new_sock.Recv(&buf);
if(ret==false){
new_sock.Close();
continue;
}
std::cout<<cli_ip<<":"<<cli_port<<"say"<<buf<<std::endl;
buf.clear();
std::cout<<"serve say";
fflush(stdout);
std::cin>>buf;
new_sock.Send(buf);
if(ret==false){
new_sock.Close();
continue;
}
}
//关闭套接字
lsn_sock.Close();
return 0;
}
//客户端
#include"tcp_socket.hpp"
2
3 int main(int argc,char *argv[])
4 {
5 if(argc!=3){
6 printf("usage: ./tcp_srv 192.168.177.128 9000");
7 return -1;
8 }
9 std::string ip=argv[1];
10 uint16_t port=std::stoi(argv[2]);
11 TcpSocket cli_sock;
12 //创建套接字
13 assert(cli_sock.Socket()!=false);
14 //向服务端发起连接
15 assert(cli_sock.Connect(ip,port));
16 //循环收发数据
17 while(1){
18 std::string buf;
19 std::cout<<"client say: ";
20 fflush(stdout);
21 std::cin>>buf;
22 assert(cli_sock.Send(buf));
23 buf.clear();
24 assert(cli_sock.Recv(&buf));
25 std::cout<<"serve say: "<<buf<<std::endl;
26 }
27 //关闭套接字
28 cli_sock.Close();
29 return 0;
30 }
#include"tcp_socket.hpp"
2 #include<signal.h>
3 void create_worker(TcpSocket new_sock){
4 pid_t pid=fork();
5 if(pid<0){
6 new_sock.Close();
7 perror("fork error");
8 return ;
9 }
10 if(pid>0){
11 new_sock.Close();
12 return;
13 }
14 while(1){
15 //使用新建连接发送数据
16 std::string buf;
17 bool ret=new_sock.Recv(&buf);
18 if(ret==false){
19 new_sock.Close();
20 continue;
21 }
22 std::cout<<"client say"<<buf<<std::endl;
23 buf.clear();
24 std::cout<<"serve say";
25 fflush(stdout);
26 std::cin>>buf;
27 new_sock.Send(buf);
28 if(ret==false){
29 new_sock.Close();
30 exit(-1);
31 }
32 }
33 exit(-1);
34 }
35 int main(int agc,char *argv[])
36 {
37 if(agc!=3){
38 printf("usage: ./tcp_srv 192.168.177.128 9000");
39 return -1;
40 }
41 signal(SIGCHLD,SIG_IGN);
42 std::string ip=argv[1];
43 uint16_t port=std::stoi(argv[2]);
//服务器流程
44 TcpSocket lsn_sock;
45 //创建套接字
46 assert( lsn_sock.Socket()!=false);
47 //为套接字绑定地址信息
48 assert(lsn_sock.Bind(ip,port));
49 //开始监听
50 assert(lsn_sock.Listen());
51 //获取新建连接
52 while(1){
53 TcpSocket new_sock;
54 std::string cli_ip;
55 uint16_t cli_port;
56 bool ret=lsn_sock.Accept(&new_sock,&cli_ip,&cli_port);
57 if(ret==false){
58 continue;
59 }
60 std::cout<<"new client:"<<cli_ip<<":"<<cli_ip<<std::endl;
61 create_worker(new_sock);
62 }
63 //关闭套接字
64 lsn_sock.Close();
65 return 0;
66 }
多线程方案实现:
相较于多进程版本,流程上没有大的差别,也是获取一个新建连接之后,创建一个线程程出来
注意事项:
1、线程间是共用同一个文件描述符表,因此对这个通信套接字描述符的操作,只能由负责这个描述符操作的线程进行关闭,其他的线程不能关闭。
2、局部变量的使用,线程创建的时候,千万要注意传参,不能因为传递局部变量地址, 而导致线程内访问的时候出现内存访问错误。
#include"tcp_socket.hpp"
2 #include<pthread.h>
3 void *thread_entry(void*arg){
4
5 TcpSocket new_sock;
6 long fd=(long)arg;
7 new_sock.set(fd);
8 while(1){
9 std::string buf;
10 bool ret=new_sock.Recv(&buf);
11 if(ret==false){
12 new_sock.Close();
13 break;
14 }
15 std::cout<<"client say"<<buf<<std::endl;
16 buf.clear();
17 std::cout<<"serve say";
18 fflush(stdout);
19 std::cin>>buf;
20 new_sock.Send(buf);
21 if(ret==false){
22 new_sock.Close();
23 break;
24 }
25 }
26 return NULL;
27 }
28 void create_worker(TcpSocket sock){
29 pthread_t tid;
30 int ret=pthread_create(&tid,NULL,thread_entry,(void *)sock.fd());
31 if(ret!=0){
32 perror("thread error");
33 sock.Close();
34 return ;
35 }
36 pthread_detach(tid);
37 return ;
38
39
40 }
41
42 int main(int agc,char *argv[])
43 {
44 if(agc!=3){
45 printf("usage: ./tcp_srv 192.168.177.128 9000");
46 return -1;
47 }
48 std::string ip=argv[1];
49 uint16_t port=std::stoi(argv[2]);
50
51 //服务器流程
52 TcpSocket lsn_sock;
53 //创建套接字
54 assert( lsn_sock.Socket()!=false);
55 //为套接字绑定地址信息
56 assert(lsn_sock.Bind(ip,port));
57 //开始监听
58 assert(lsn_sock.Listen());
59 //获取新建连接
60 while(1){
61 TcpSocket new_sock;
62 std::string cli_ip;
63 uint16_t cli_port;
64 bool ret=lsn_sock.Accept(&new_sock,&cli_ip,&cli_port);
65 if(ret==false){
66 continue;
67 }
68 std::cout<<"new client:"<<cli_ip<<":"<<cli_ip<<std::endl;
69 create_worker(new_sock);
70 }
71 //关闭套接字
72 lsn_sock.Close();
73 return 0;
74 }
tcp通信程序编写与运行中遇到的一些特殊情况:
连接断开:tcp是面向连接的通信,一旦连接断开就无法通信
问题:如何在代码中知道连接断开了?连接断开后,在代码中的体现是什么?
当recv函数接收数据的时候,返回0,代表的不仅仅是没有接收到数据,更多是为了表示连接断开了。当send函数发送数据的时候,程序直接异常(SIGPIPE)退出,因此如果网络通信中,不想让程序因为连接断开而导致发送数据的时候程序异常通很出,就对SIGPIPE信号进行处理。
问题:有时候网络程序关闭后,无法立即启动,会bind绑定地址报错,绑定失败,地址已经被使用网络通信程序,如果程序是主动关闭的一方,程序会无法立即启动
因为一个程序主动关闭了连接,这个连接并不会立即被释放(对应的地址和端口依然被占用),而是要等待一段时间。这时候,要不然换个端口绑定,要不然就等一会
netstat-anptu查看没有连接对应的信息了,则就可以绑定了,netstat命令,非常重要,这是查看主机上所有网络连接状态的命令。因为一个网络通信程序运行起来后,如果没有达到预期的目标,首先就要看程序对应的网络连接是否是正常的
a 查看所有信息
n不要以服务名称,显示端口或地址,比如127.0.0.1会被 识别为localhost,22端口会被识别为ssh,...
p显示网络连接信息的时候,顺便显示连接对应的进程ID和名称
t过滤只显示tcp套接字信息
u过滤只显示udp套接字信息