前言
最近新换了家实习单位,趁着leader去交通局开会,偷个闲整理一下有关于socket通信方面的知识。
socket
socket可以将其理解为一种“中间件”,即TCP/IP协议族与应用层通信的中间软件,它是一组接口。恰巧最近在看自动驾驶中的“中间件”,我们可以将其理解:我们是做“炸串”的寡头、其中肉串的好坏对我们至关重要,我们可能不需要去自己亲自挑选肉串,可以让某些代理机构去测评并提供新鲜的“肉串”至于肉串是谁家做的,我们不关心,我们只关注肉的质量。其中代理机构类似与“中间件”,将我们与提供肉串的厂商分隔开来,每个部分只专注于其本身擅长的事。就像挑选“肉串一样”,TCP/IP协议很复杂,socket将其封装提供给用户使用。
socket起源于Unix,而Unix/Unix基本哲学就是“一切皆为文件”,我们知道文件的操作模式为:
“open->write/read->close”模式,我们将socke可以理解为对数据流的一种读写操作,socke帮我们封装好了对应的函数,后面我们将介绍。
TCP/IP及UDP简介
TCP/IP(Transmission Control Protocol/Internet Protocol)传输控制协议/网间协议,是一个工业标准的协议集,他是为广域网(WANs)设计的。UDP(User Data Protocol)用户数据协议与TCP相对应的协议。
TCP/IP协议簇包括应用层、传输层、网络层、链路层(数据链路层、物理层)socket处于的位置为应用层和运输层之间。
TCP/IP 三次握手建立连接
TCP建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
- 客户端向服务器发送一个SYN J
- 服务器向客户端响应响应一个SYN K 并对SYN K 进行确认 ACK + 1
- 客户端再向服务器发一个确认ACK K+1
只有进行完三次握手之后才可以建立连接。
SYN:同步序列编号(Synchronize Sequence Numbers)。是TCP/IP建立连接时使用的握手信号。在客户机和服务器之间建立正常的TCP网络连接时,客户机首先发出一个SYN消息,服务器使用SYN+ACK应答表示接收到了这个消息,最后客户机再以ACK消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。 |
ACK (Acknowledge character)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。 在TCP/IP协议中,如果接收方成功的接收到数据,那么会回复一个ACK数据。通常ACK信号有自己固定的格式,长度大小,由接收方回复给发送方。 |
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
socket 中TCP的四次握手释放
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端。
(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
socket通信流程
socket是"open->write/read->close"模式实现的,交互流程如下图所示:
实例代码
示例一:server只能接收不能发送
示例一:server端
/**服务器只能够接收客户端发来的消息**/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <thread>
#include <iostream>
using namespace std;
#define PORT 7000
#define QUEUE 20 //连接请求队列
int conn;
/***
1.创建一个socket,用函数socket() line31
2.设置socket属性,用函数setsockopt();可选
3.绑定IP地址,端口等信息到socket上,用函数bind();line38
4.开启监听,用函数listen();line44
5.接收客户端上来的连接,用accept()
6.收发数据,用函数send()和recv(),
7.关闭网络连接;
8.关闭监听
***/
void thread_task()
{
}
int main()
{
cout << "AF_INET : %d " <<AF_INET << endl;
printf("%d /n", SOCK_STREAM);
int ss = socket(AF_INET,SOCK_STREAM,0);//若成成功则返回一个sockefd(套接子描述符)
printf("%d/n",ss);
struct sockaddr_in server_sockaddr;//一般存储地址和端口用于信息的存储和显示
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);//将一个无符号短整型转换为网络字节序,即大端模式
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);//将主机的无符号长整型转换成网络字节顺序
if(bind(ss, (struct sockaddr*) & server_sockaddr, sizeof(server_sockaddr))== -1)
{
perror("bind");
exit(1);
}
if(listen(ss,QUEUE) == -1)
{
perror("listen");
exit(1);
}
char buffer[1024];
//创建另外一个线程
//std::thread t(thread_task);
//t.join();
//char buf[1024];
//主线程
while (1)
{
memset(buffer, 0, sizeof(buffer));
int len = recv(conn, buffer, sizeof(buffer), 0);//从TCP连接的另一端接受数据
/*该函数的第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0*/
if(strcmp(buffer,"exit\n") == 0)
{
break;
}
printf("%s",buffer);
//必须要有返回数据,这样才算一个完整的请求
send(conn, buffer, len, 0);//向TCP连接的另一端发送数据
}
close(conn);//因为accept函数链接成功后还会生成一个新的套接字描述,结束之后也需要关闭
close(ss);//关闭套接字描述符
return 0;
}
示例一:客户端
/*局域网TCP客户端*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#define MYPORT 7000
#define BUFFER_SIZE 1024
/*
1.创建一个socket,用函数socket();
2.设置socket属性,用函数setsockopt();*可选
3.绑定IP地址、端口等信息到socket上,用函数bind();
4.循环接收数据,用函数recvfrom();
5.关闭网络连接;
*/
int main()
{
//定义sockfd
int sock_cli = socket(AF_INET,SOCK_STREAM,0);
//定义sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0 , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT);//服务器端口
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //服务器ip,inet_addr用于IPv4的IP转换
(十进制转换为二进制)
//127.0.0.1是本地预留地址
//连接服务器,成功返回0,错误返回-1
if(connect(sock_cli, (struct sockaddr*)&servaddr, sizeof(servaddr)) <0)
{
perror("connect");
exit(1);
}
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) //从stream流中读取size个字符到所
指向的内存区域
{
/*每次读取一行,读取的数据保存在buf指向的字符串数组中,成功则返回地一个参数的buf:*/
send(sock_cli, sendbuf, strlen(sendbuf), 0);//发送
std::cout << " judge send is : " << send <<std::endl;
if(strcmp(sendbuf, "exit\n") == 0)
{
break;
}
recv(sock_cli,recvbuf, sizeof(recvbuf), 0);
fputs(recvbuf, stdout);//把字符串写入到指定的流,但是不包括空字符
memset(sendbuf, 0 , sizeof(sendbuf));
memset(recvbuf, 0 , sizeof(recvbuf));
}
close(sock_cli);
return 0;
/*在TCP三次握手完成之后会进入等待连接队列,等待服务端调用accept与之建立链接,这时候是sever>端调用accept跟客户端建立
通信,客户端并不需要调用accept,因为很多个客户端要跟服务端建立连接,这时候服务端就会有一个>队列,对已经经过三次握手
的才可以建立连接(类似与缓存消息),这是由服务端来确认的,客户端并不知道什么时候服务端才能>跟它建立连接,在服务端没有
调用accept与之连接或者还未排队到它,只能是一直等待,直到服务端准备好了才能跟客户端建立连接>,所以主动权在服务端*/
}
运行效果
先启动服务器,然后客户端发送“hello”,服务器收到信息之后,在返回给客户端“hello”。