SOCKET入门级编程

本文介绍了SOCKET编程的基础知识,包括两种常用的套接字类型——流格式套接字(TCP)和数据报格式套接字(UDP)。详细阐述了TCP/IP协议栈的各层功能,以及TCP的三次握手和四次挥手过程。同时,提到了在Windows环境下实现通信的客户端和服务端程序,并讨论了SOCKET的阻塞模式。
摘要由CSDN通过智能技术生成

SOCKET:

(有些内容摘自网络)

是应用层与TCP/IP协议通信的中间软件抽象层,就是一组接口。

两种常用套接字

流格式套接字(SOCK_STREAM)【使用了 TCP 协议】

是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。
○ 数据在传输过程中不会消失;
○ 数据是按照顺序传输的;
○ 数据的发送和接收不是同步的

数据报格式套接字(SOCK_DGRAM)【使用了 UDP 协议】

计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。
○ 强调快速传输而非传输顺序;
○ 传输的数据可能丢失也可能损毁;
○ 限制每次传输的数据大小;
○ 数据的发送和接收是同步的

TCP/IP

应用层:

向用户提供一组常用的应用程序,比如电子邮件、文件传输访问、远程登录等。远程登录TELNET使用TELNET协议提供在网络其它主机上注册的接口。TELNET会话提供了基于字符的虚拟终端。文件传输访问FTP使用FTP协议来提供网络内机器间的文件拷贝功能。

传输层:

提供应用程序间的通信。其功能包括:一、格式化信息流;二、提供可靠传输。为实现后者,传输层协议规定接收端必须发回确认,并且假如分组丢失,必须重新发送。

网络层 :

负责相邻计算机之间的通信。其功能包括三方面。
一、处理来自传输层的分组发送请求,收到请求后,将分组装入IP数据报,填充报头,选择去往信宿机的路径,然后将数据报发往适当的网络接口。

二、处理输入数据报:首先检查其合法性,然后进行寻径–假如该数据报已到达信宿机,则去掉报头,将剩下部分交给适当的传输协议;假如该数据报尚未到达信宿,则转发该数据报。

三、处理路径、流控、拥塞等问题。

网络接口层:

这是TCP/IP软件的最低层,负责接收IP数据报并通过网络发送之,或者从网络上接收物理帧,抽出IP数据报,交给IP层。

在这里插入图片描述

如何找到通信的目标(找到某一台计算机地址的某个网络服务)

找到某一台计算机上的某个网络服务应用程序,必须具备IP地址、MAC地址、端口号;

  1. IP 地址只能定位到一个局域网,无法定位到具体的一台计算机
  2. MAC地址可以定位到某一局域网的某台计算机,每台计算机的MAC地址是一定的;
  3. 最后得到某台计算机上的网络服务应用程序的端口,为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(Port Number);
    在这里插入图片描述在这里插入图片描述

实现通信

三次握手

在socket编程中,这一过程由客户端执行connect来触发。

  1. 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
  2. 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
  3. 第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

四次挥手

需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。

  1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  2. 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  3. 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  4. 四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210409213438467.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzU0MzQ2Mzg5,size_

实现通信程序(基于WINDOWS)

服务端程序

建议IP地址转换函数inet_pton;抛弃旧的

#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll
#include <Ws2tcpip.h>
#include <iostream>

int main() {
	//初始化 DLL
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//创建套接字
	SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	//绑定套接字
	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
	sockAddr.sin_family = PF_INET;  //使用IPv4地址
	inet_pton(AF_INET, "127.0.0.1", &sockAddr.sin_addr.s_addr);
	sockAddr.sin_port = htons(1234);  //端口
	bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
	//进入监听状态
	listen(servSock, 20);
	//接收客户端请求
	SOCKADDR clntAddr;
	int nSize = sizeof(SOCKADDR);
	SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
	//向客户端发送数据
	char str[128]= "bdm!";
	send(clntSock, str, strlen(str) + 1, NULL);
	//关闭套接字
	closesocket(clntSock);
	closesocket(servSock);
	//终止 DLL 的使用
	WSACleanup();
	return 0;
}
客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll
int main() {
	//初始化DLL
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//创建套接字
	SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	//向服务器发起请求
	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
	sockAddr.sin_family = PF_INET;
	inet_pton(AF_INET, "127.0.0.1", &sockAddr.sin_addr.s_addr);
	sockAddr.sin_port = htons(1234);
	connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
	//接收服务器传回的数据
	char szBuffer[128] = { 0 };
	recv(sock, szBuffer, MAXBYTE, NULL);
	//输出接收到的数据
	printf("Message form server: %s\n", szBuffer);
	//关闭套接字
	closesocket(sock);
	//终止使用 DLL
	WSACleanup();
	system("pause");
	return 0;
}

服务器端持续监听客户端的请求:

服务端

#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll
#include <Ws2tcpip.h>
#include <iostream>

using namespace std;

int main() {
	//初始化 DLL
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//创建套接字
	SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	//绑定套接字
	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
	sockAddr.sin_family = PF_INET;  //使用IPv4地址
	inet_pton(AF_INET, "127.0.0.1", &sockAddr.sin_addr.s_addr);
	sockAddr.sin_port = htons(1234);  //端口
	bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
	//进入监听状态
	listen(servSock, 20);
	//接收客户端请求
	SOCKADDR clntAddr;
	int nSize = sizeof(SOCKADDR);
    /*char *str = (char*)"bdm!";*/
	SOCKET clntSock = INVALID_SOCKET;

	clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
	if (clntSock== INVALID_SOCKET)
	{
		printf("no value client");
	}
	printf("new service join");
	char _recvbuf[128] = {};
	while (true)
	{
		//接受客户端的请求数据
		int nLen = recv(clntSock, _recvbuf, 128, 0);

		if (nLen <= 0) {
			printf("client go-out");
			break;
		}
		else {
					//处理请求
			if (0 == strcmp(_recvbuf, "getName")) {
				char str[128] = "uzqi!";

				send(clntSock, str, strlen(str) + 1, 0);
			}
			else if(0 == strcmp(_recvbuf, "getAge"))
			{
				char str[128] = "80";

				send(clntSock, str, strlen(str) + 1, 0);
			}
			else
			{
				char str[128] = "你是谁";
;
				//向客户端发送数
				send(clntSock, str, strlen(str) + 1, 0);
			}
		}			
	}
	//关闭套接字
	closesocket(clntSock);
	closesocket(servSock);
	//终止 DLL 的使用
	WSACleanup();
	return 0;
}

客户端

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll

using namespace std;

int main() {
	//初始化DLL
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	//创建套接字
	SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	//向服务器发起请求
	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
	sockAddr.sin_family = PF_INET;
	inet_pton(AF_INET, "127.0.0.1", &sockAddr.sin_addr.s_addr);
	sockAddr.sin_port = htons(1234);
	int ret = connect(sock, (SOCKADDR*)&sockAddr, sizeof(sockaddr_in));
	if (SOCKET_ERROR==ret)
	{
		printf("error to connect ");
	}
	else
	{
		printf("succesd to connect ");
	}
	while (true)
	{
		//输入请求
		char cmdbuf[128];
		scanf("%s",cmdbuf);
		//处理请求
		if (0 == strcmp(cmdbuf, "exit")) {
			cout << "sadf" << 2<<endl;
			break;
		}
		else
		{
			//向服务器发送请求
			send(sock, cmdbuf, strlen(cmdbuf) + 1, 0);
		}
		//接收服务器传回的数据
		char szBuffer[128] = {};
		recv(sock, szBuffer, 128, 0);

		//输出接收到的数据
		printf("Message form server: %s\n", szBuffer);
	}
	接收服务器传回的数据
	//char szBuffer[128] = {};
	//recv(sock, szBuffer, 128, 0);

	//cout << " szBuffer " << szBuffer << endl;
	输出接收到的数据
	//printf("Message form server: %s\n", szBuffer);
	//关闭套接字
	closesocket(sock);
	//终止使用 DLL
	WSACleanup();
	system("pause");
	return 0;
}


回声传播:

https://blog.csdn.net/w00347190/article/details/100169448

SOCKET阻塞模式

所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。

  1. accept在阻塞模式下,没有新连接时,线程会进入睡眠状态;非阻塞模式下,没有新连接时,立即返回WOULDBLOCK错误。

  2. connect在阻塞模式下,仅TCP连接建立成功或出错时才返回,分几种具体的情况,这里不再叙述;非阻塞模式下,该函数会立即返回INPROCESS错误(需用select检测该连接是否建立成功)

  3. recv/recvfrom/send/sendto这两类函数读写收/发送缓冲区。

    1. 当发送数据时,首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么收发数据 会被阻塞(暂停执行),一直会等到缓冲区中的数据被发送到目标机器,腾出足够的空间为止
    2. 如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定
    3. 如果要写入的数据大于缓冲区的最大长度,那么将分批写入
    4. 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到再次读取。
  4. select/poll/epoll并不是真正意义上的阻塞,它们的阻塞是由于它们最后一个timeout参数决定的,timeout大于0时,它们会一直等待直到超时才退出(相等于阻塞了吧,_),而timeout=-1即永远等待

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值