IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> TCP套接字编程详解 -> 正文阅读

[网络协议]TCP套接字编程详解

目录

为什么socket编程又叫套接字编程?

TCP服务端

初始化套接字库——WSAStarup

创建套接字——socket

绑定到本机 ——bind

开始监听 ——listen

连接客户端请求——accept

发送与接收数据 ——send/recv

完整代码


为什么socket编程又叫套接字编程?

?为什么要称socket为套接字?首先套接字的原词为"socket",直译过来就是插座的意思,最先采用这个词的人,觉得网络连接,就像插口和插座一样,一方插,一方被插(知乎用户回答)

? ? 除此之外,linux等系统中“套接字”对应“socket word”,所以“字”也就是对应“word”,可能指计算机数据,也可能指存储socket的数据表示,因为端口号是两个字节,就是一个WORD。

? ? 至于为什么翻译为“套接字”:有人说是“套用-接口-标识”的意思;有人说是“套接起来的字符串”的意思;有人说“是将网络数据包一层一层地套起来传输”的意思。

? ? 总之就是望文生义,毕竟怎么解释都不重要,东西还是那个东西

? ? 看了一圈,最比较赞同的解释是:套接指的是套接管,就是奖两根水管套接起来的关资,然后“字”就是连接数据的标识符,所以套接字就是标识连接的数据体。(附作者链接Socket为什么要翻译成套接字? - FrankIsFree的回答 - 知乎

TCP服务端

初始化套接字库——WSAStarup

//初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);  //MAKEWORD(a,b) b|a<<8
err = WSAStartup(wVersion, &wsaData);
//检查1
if (err != 0) {
    return err;
}
//检查2
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
    //清理套接字库
    WSACleanup();
    return -1;
}

当我们再进行socket编程时,要调用各种socket函数,而且还需要用到一个库文件Ws2_32.lib个一个头文件Winsock2.h?

wsastartup()函数向操作系统说明,我们要用哪个库文件。 因此就可以将库文件与当前的应用程序绑定,从而就可以调用该版本的socket的各种函数了。 一句话解释:wsastartup()主要就是进行相应的socket库绑定。?

WSAStarup

  • W:windows
  • S:socket
  • A:Asynchronous异步
  • Starup:启动
int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData );?

WSAStartup需要传入两个参数,wVersionRequested指明需要库的版本号,lpWSAData为指向WSAData数据结构的指针,用来接收Windows Sockets实现细节(存储初始化数据)

调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。? 否则返回对应错误的宏。

WORD是微软SDK中的类型,意为'字'?,是2byte无符号整数,表示范围0~65535相当于C语言中的2个char

MAKEWORD();可以创建WORD类型。其工作原理类似于bLow | bHigh<<8

WORD MAKEWORD(     //函数原型
BYTE bLow, //指定新变量的低字节序;
BYTE bHigh //指定新变量的高字节序;
);

在进行初始化结果判断时需判断两个:第一个是WSAStarup函数的返回值,判断返回结果是否为0;第二个是存储初始化数据WSAData的高位和地位是否和我们指定的库版本一致

//检查1
if (err != 0) {
    return err;
}
//检查2
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
    //清理套接字库
    WSACleanup();
    return -1;
}
  • WSACleanup()函数?

? ? ? ? int WSACleanup (void);?

? ? ? ? 应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源

创建套接字——socket

//创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

socket函数用于创建一个socket描述符,它唯一标识一个socket,包含了三个参数:

int socket(int domain, int type, int protocol);?

  • domain:协议域/协议族

协议族决定了socket的地址类型,在通信中必须采用相应的地址.

  • type:socket通信类型?

函数socket()的参数type用于设置socket通信类型


并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。?

类型为SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connect()函数进行。一旦连接,可以使用read()或者write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内任然没有接受完毕,可以将这个连接人为已经死掉。
SOCK_DGRAM和SOCK_RAW 这个两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接受数据,recvfrom()接受来自制定IP地址的发送方的数据。
SOCK_PACKET是一种专用的数据包,它直接从设备驱动接受数据。?

  • protocol:制定某个协议的特定类型

函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

绑定到本机 ——bind

1.首先准本绑定信息。

在准本绑定信息中既要指明绑定的IP地址,同时也要指明绑定的端口号

SOCKADDR_IN addrSrv;
//地址
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//协议族,与上面保持一致
addrSrv.sin_family = AF_INET;
//端口;0~65535,其中1024以下的端口为系统保留的
addrSrv.sin_port = htons(6000);

sockaddr?和?sockaddr_in?这两个结构体用来处理网络通信的地址。?

sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了;sockaddr_in该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中?

sockaddrsockaddr_in二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

? ? sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。?
? ? sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。
?

htonl函数

主机的unsigned long值转换成网络字节顺序。主要针对32位的(long)

htons函数

htons()作用是将端口号由主机字节序转换为网络字节序的整数值。主要针对16位的(short)

htonl和htons函数

  • h:host主机
  • to:转换
  • n:network:网络
  • l:32位l的ong
  • s:16位的short

之所以要进行字节序的转换,是因为主机字节序和网络字节序的存储不同

主机字节序
1)大端存储:低位字节放在内存的高地址端,高位字节放在内存的低地址端
2)小端存储:低位字节放在内存的低地址端,高位字节放在内存的高地址端

网络字节序:
UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端存储而一般x86计算机用的是小端存储~

? ? ?在一些socket通信的服务器程序中我们会看到在服务器bind IP地址和端口号时,我们不是bind明确的IP地址(如222.20.79.150),而是使用INADDR_ANY。

? ?INARRD_ANY是用于多网卡的机器上的,多网卡就会有多个IP地址。比如你的机器有3个IP:192.168.1.1、202.202.202.202和61.1.2.3。如果设置serv.sin_addr.s_addr=inet_addr("192.168.1.1");然后监听100端口,这时其他机器只有连接到192.168.1.1才能成功;连接202.202.202.202:100或61.1.2.3:100都会失败。如果设置serv.sin_addr.s_addr=htonl(INADDR_ANY);的话,无论连接哪个IP都可以连接上。

? ? 总的来说INADDR_ANY参数就表明可以连接到本机的所有ip都是可以的,极大的简化了需要创建socket的数量,因为我们就绑定一个INADDR_ANY和一个端口,然后客户端通信到这个机器的所有ip都用这个socket来处理。

2.绑定信息准备好后则进行绑定到本机

 //绑定
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

bind()函数原型

int bind (int sockfd, const struct sockaddr * addr, socklen_t addrlen);

1.sockfd:即为socket描述字,他是通过socket()函数创建的,唯一标识一个socket。bind函数就是将这个描述子绑定一个名字

2.addr:一个sockaddr*指针,指向地址结构的指针,根据创建socket时的地址协议不同而不同。

3.addrlen:对应地址结构的长度。通常服务器在启动时会绑定一个众所周知的地址(ip地址+端口号)。而客户端不用指定系统自动分配,所以通常服务端在listen之前要调用bind(),而客户端不会调用,在connect()时由系统随机生成一个。

开始监听 ——listen

//监听
listen(sockSrv, 10);

listen()函数原型

int listen(int sockfd, int backlog)

listen函数的第一个参数时即将要监听的socket描述字,第二个参数为相应的socket可以排队的最大连接数。socket()创建的socket默认是一个主动类型,listen则将socket变成被动类型,等待客户连接请求。

连接客户端请求——accept

//接收请求前的准备工作
SOCKADDR addrCli;
int len = sizeof(SOCKADDR);

while (true) {
	//接收链接请求,返回针客户端的套接字
	SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);

	//关闭连接
	closesocket(sockConn);
}

accept()函数?

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd用来标识服务端套接字(也就是listen函数中设置为监听状态的套接字)
  • addr用来保存客户端套协议地址(包括客户端IP和端口信息等)
  • addrlen是客户端套接字的长度

返回客户端套接字的标识,一个客户端的socket

注意:如果没有客户端套接字去请求,它便会在那里一直等下去。如果是非阻塞式的socket, 那么accept函数会立即返回。

发送与接收数据 ——send/recv

char recvBuf[100];
char sendBuf[100];
while (true) {
	//接收连接请求,返回针对客户端的套接字
	SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);

	//准备发送的数据
	sprintf_s(sendBuf, 100, "hello world");

	//发送数据
	send(sockConn, sendBuf, sizeof(sendBuf) + 1, 0);
	//接收数据
	recv(sockConn, recvBuf, 100, 0);
	std::cout << recvBuf << std::endl;

	//关闭套接字
	closesocket(sockConn);
}
int send( SOCKET s,?const char FAR *buf,? int len,?int flags );?
int recv( SOCKET s, char FAR *buf,??int len,? int flags?);???
  • s指定客户端socket的描述符
  • buf为发送/接收数据的缓冲区
  • len为实际发送数据/接收数据的缓冲区大小
  • flags一般置位0

完整代码

#include <iostream>
#include<WinSock2.h>//第二版本的网络库
#pragma comment(lib,"ws2_32.lib")

//一般模板
/*
*	0.初始化套接字库
*	1.创建socket
*	2.绑定到本机
*	3.开始监听
*	while(true){
*		4.接收客户端连接
*		
*		5.关闭客户端socket
*	}
* 
*	6.关闭服socket
*	7.清理套接字库
*/

int main() {
	//初始化套接字库
	WORD wVersion;
	WSADATA wsaData;
	int err;
	wVersion = MAKEWORD(1, 1);  //MAKEWORD(a,b) b|a<<8
	err = WSAStartup(wVersion, &wsaData);
	//检查1
	if (err != 0) {
		return err;
	}
	//检查2
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
		//清理套接字库
		WSACleanup();
		return -1;
	}

	//创建tcp套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	//绑定到本机
	//绑定即要指明绑定的哪个IP地址,同时指明绑定的端口号
	//准备绑定信息
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	//协议族,与上面保持一致
	addrSrv.sin_family = AF_INET;
	//端口;0~65535,其中1024以下的端口为系统保留的
	addrSrv.sin_port = htons(6000);
	//绑定
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//监听
	listen(sockSrv, 10);
	std::cout << "Server start at 6000" << std::endl;

	//接收请求前的准备工作
	SOCKADDR addrCli;
	int len = sizeof(SOCKADDR);

	char recvBuf[100];
	char sendBuf[100];
	while (true) {
		//接收链接请求,返回针对客户端的套接字
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);

		//准备发送的数据
		sprintf_s(sendBuf, 100, "hello world");

		//发送数据
		send(sockConn, sendBuf, sizeof(sendBuf) + 1, 0);
		//接收数据
		recv(sockConn, recvBuf, 100, 0);
		std::cout << recvBuf << std::endl;

		//关闭套接字
		closesocket(sockConn);
	}

	//关闭套接字
	closesocket(sockSrv);
	//清理套接字库
	WSACleanup();
	system("pause");
}
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 12:34:57  更:2022-10-31 12:37:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 5:46:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计