主要参考:
网络编程和套接字
网络编程
是编写程序使两台 联网的 计算机相互交换数据, 主要依靠操作系统提供的“套接字”部件.套接字
是用于网络数据传输的软件设备, 如果将网络通信类比为电话机通信系统,那套接字就是电话机, 渠道就是互联网, 和电话拨打或接听一样,套接字也可以发送或接收.
消息接收过程
- 先有一台电话机: 创建套接字, 即 实例化
int socket(int domain, int type, int protocol);
- 电话机要有电话号码的问题: 给套接字 绑定 地址信息(IP地址和端口号)
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
- 设置电话机为可接听状态: 设置套接字为 可接听 状态
int listen(int sockfd, int backlog);
- 接听电话: 套接字 接受 消息
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
消息发送过程
- 调用socket函数创建套接字
- 调用connect函数向服务端发送 连接请求
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
UDP协议
简介
UDP(User Datagram Protocol,用户数据报协议)是传输层的协议,是在IP的数据报服务之上增加了最基本的服务:复用和分用以及差错检测。
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
优势:
-
UDP无连接,时间上不存在建立连接需要的时延。空间上,TCP需要在端系统中维护连接状态,需要一定的开销。此连接装入包括接收和发送缓存,拥塞控制参数和序号与确认号的参数。UCP不维护连接状态,也不跟踪这些参数,开销小。空间和时间上都具有优势。
-
分组首部开销小,TCP首部20字节,UDP首部8字节。
-
UDP没有拥塞控制,应用层能够更好的控制要发送的数据和发送时间,网络中的拥塞控制也不会影响主机的发送速率。某些实时应用要求以稳定的速度发送,能容忍一些数据的丢失,但是不能允许有较大的时延(比如实时视频,直播等)
-
UDP提供尽最大努力的交付,不保证可靠交付。所有维护传输可靠性的工作需要用户在应用层来完成。没有TCP的确认机制、重传机制。如果因为网络原因没有传送到对端,UDP也不会给应用层返回错误信息
-
UDP是面向报文的,对应用层交下来的报文,添加首部后直接乡下交付为IP层,既不合并,也不拆分,保留这些报文的边界。对IP层交上来UDP用户数据报,在去除首部后就原封不动地交付给上层应用进程,报文不可分割,是UDP数据报处理的最小单位。
-
UDP常用一次性传输比较少量数据的网络应用,如DNS,SNMP等,因为对于这些应用,若是采用TCP,为连接的创建,维护和拆除带来不小的开销。UDP也常用于多媒体应用(如IP电话,实时视频会议,流媒体等)数据的可靠传输对他们而言并不重要,TCP的拥塞控制会使他们有较大的延迟,也是不可容忍的
代码实现
- Server 端:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <winsock2.h>
int main(int argc, char* argv[])
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
if (WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (serSocket == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("bind error !");
closesocket(serSocket);
return 0;
}
const int MAX_LENGTH = 255;
char sendData[MAX_LENGTH];
char recvData[MAX_LENGTH];
sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
while (true)
{
int ret = recvfrom(serSocket, recvData, MAX_LENGTH, 0, (sockaddr*)&remoteAddr, &nAddrLen); // 返回读入的字节数
if (ret > 0)
{
recvData[ret] = 0x00;
printf("来自 %s: %s\n", inet_ntoa(remoteAddr.sin_addr), recvData);
strcpy_s(sendData, "已读.\n");
sendto(serSocket, sendData, strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);
}
}
closesocket(serSocket);
WSACleanup();
return 0;
}
- Client 端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <winsock2.h>
int main(int argc, char* argv[])
{
WORD socketVersion = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(socketVersion, &wsaData) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int len = sizeof(sin);
const int MAX_LENGTH = 255;
char sendData[MAX_LENGTH] = "在吗?";
char recvData[MAX_LENGTH];
// 开启会话
sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);
printf("send: 在吗?\n");
while (true)
{
printf("\r ");
printf("\r接收数据...");
Sleep(1000);
int ret = recvfrom(sclient, recvData, MAX_LENGTH, 0, (sockaddr *)&sin, &len);
if (ret > 0)
{
recvData[ret] = 0x00;
printf("\r ");
printf("\rrecv: %s\n", recvData);
printf("send: ");
gets_s(sendData, MAX_LENGTH);
printf("发送数据...\t");
sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);
Sleep(1000);
printf("done.");
Sleep(1000); // 模拟网络延迟
}
}
closesocket(sclient);
WSACleanup();
return 0;
}
TCP协议
建议参考博文: TCP详解
简介
TCP协议全称: 传输控制协议, 顾名思义, 就是要对数据的传输进行一定的控制.
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
正常情况下, tcp需要经过三次握手建立连接, 四次挥手断开连接.
第一次: 客户端 - - > 服务器 __ 此时服务器知道了客户端要建立连接了
第二次: 客户端 < - - 服务器 __ 此时客户端知道服务器收到连接请求了
第三次: 客户端 - - > 服务器 __ 此时服务器知道客户端收到了自己的回应
到这里, 就可以认为客户端与服务器已经建立了连接.
四次挥手
代码实现
- Server 端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData;
int port = 5099;
char buf[] = "Server: hello, I am a server.....";
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock");
return -1;
}
//创建用于监听的套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port); //1024以上的端口号
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
if (retVal == SOCKET_ERROR) {
printf("Failed bind:%d\n", WSAGetLastError());
return -1;
}
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("Listen failed:%d", WSAGetLastError());
return -1;
}
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
//等待客户请求到来
SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
if (sockConn == SOCKET_ERROR) {
printf("Accept failed:%d", WSAGetLastError());
//break;
}
printf("Accept client IP:[%s]\n", inet_ntoa(addrClient.sin_addr));
//发送数据
int iSend = send(sockConn, buf, sizeof(buf), 0);
if (iSend == SOCKET_ERROR) {
printf("send failed");
// break;
}
char recvBuf[100];
memset(recvBuf, 0, sizeof(recvBuf));
// //接收数据
recv(sockConn, recvBuf, sizeof(recvBuf), 0);
printf("%s\n", recvBuf);
closesocket(sockConn);
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
- Client 端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
//加载套接字
WSADATA wsaData;
char buff[1024];
memset(buff, 0, sizeof(buff));
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock");
return -1;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(5099);
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == sockClient) {
printf("Socket() error:%d", WSAGetLastError());
return -1;
}
//向服务器发出连接请求
if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET) {
printf("Connect failed:%d", WSAGetLastError());
return -1;
}
else
{
//接收数据
recv(sockClient, buff, sizeof(buff), 0);
printf("%s\n", buff);
}
//发送数据
auto *buffSend = "hello, this is a Client....";
send(sockClient, buffSend, strlen(buffSend) + 1, 0);
printf("%d", strlen(buffSend) + 1);
//关闭套接字
closesocket(sockClient);
WSACleanup();
system("pause");
return 0;
}