使用C语言来实现了基于TCP和UDP的WinSock套接字编程,同时附带对WinSock相关函数的分析。

一. TCP协议和UDP协议的简介和区别

1. TCP协议

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它的主要特点:
(1)基于流的方式;
(2)面向连接;
(3)可靠通信方式;
(4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;
(5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。
此外,TCP协议创建连接和终止连接分别通过“三次握手”和“四次挥手”实现。

2. UDP协议

用户数据报协议(UDP,User Datagram Protocol)是一种无需建立连接就可以发送封装的 IP 数据包的传输层通信协议。它不提供数据包分组、组装、对数据包进行排序、报文到达确认、流量控制等功能。
它的最主要特点:传输数据之前发送端和接收端不建立连接。

3. 二者的区别

TCP UDP
基于连接 基于无连接
程序结构复杂 程序结构简单
字节流模式 数据报文模式
安全可靠 可能丢包等

二. WinSock套接字简介

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

WinSock是Windows操作系统下套接字编程的规范,该规范是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。

更多关于Windows Sockets 2的详细介绍和使用说明,可以参照微软的官方文档:
https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-start-page-2

三. 基于TCP协议实现

1. 实现流程

基于TCP实现流程

2. WinSock函数分析

(1)WSAStartup() 初始化WinSock

1
2
3
4
int WSAStartup(
WORD wVersionRequired,
LPWSADATA lpWSAData
);

参量
wVersionRequired:标识所调用WinSock的版本号,使用MAKEWORD函数来生成。
lpWSAData:指向WSADATA数据结构的指针,该数据结构将接收Windows套接字实现的详细信息。

返回值
如果成功,则WSAStartup函数将返回零;否则,返回错误代码(不需要调用WSAGetLastError函数并且不应该调用它来获取错误代码。)

(2)socket() 创建套接字

1
2
3
4
5
SOCKET WSAAPI socket(
int af,
int type,
int protocol
);

参量
af:地址族规范。AF_INET是IPv4的Internet地址族格式。
type:套接字类型规范。SOCK_STREAM类型可将传输控制协议(TCP)用于Internet地址系列。
SOCK_DGRAM类型可将用户数据报协议(UDP)用于Internet地址系列。
protocol:使用的协议。IPPROTO_TCP表示传输控制协议(TCP)。
IPPROTO_UDP表示用户数据报协议(UDP)。
注意:protocol参数的可能选项决定于指定的地址系列和套接字类型。

返回值
如果成功,则socket函数返回新套接字的描述符;否则,返回INVALID_SOCKET的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(3)bind() 绑定

1
2
3
4
5
int bind(
SOCKET s,
const sockaddr *addr,
int namelen
);

参量
s:标识未绑定套接字的描述符。
addr:指向要分配给绑定套接字的本地地址的sockaddr结构的指针。
namelen:name参数所指向的值的长度(以字节为单位)。

返回值
如果成功,则 bind函数返回零;否则,返回SOCKET_ERROR,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(4)listen() 监听

1
2
3
4
int WSAAPI listen(
SOCKET s,
int backlog
);

参量
s:标识绑定的未连接套接字的描述符。
backlog:挂起的连接队列的最大长度。

返回值
如果成功,则listen函数返回零;否则,返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(5)connect() 发起连接请求

1
2
3
4
5
int WSAAPI connect(
SOCKET s,
const sockaddr *name,
int namelen
);

参量
s:标识未连接套接字的描述符。
name:指向应建立连接的sockaddr结构的指针 。
namelen:name参数所指向的sockaddr结构的长度(以字节为单位)。

返回值
如果成功,则connect函数返回零;否则,返回SOCKET_ERROR,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

6)accept() 接收连接请求

1
2
3
4
5
SOCKET WSAAPI accept(
SOCKET s,
sockaddr *addr,
int *addrlen
);

参量
s:一个描述符,用于标识已使用侦听功能置于侦听状态的套接字 。实际上,连接是通过accept返回的套接字建立的 。
addr:通信层已知的指向接收连接实体地址的缓冲区的可选指针。addr参数的确切格式由创建sockaddr结构的套接字时建立的地址族确定 。
addrlen:指向整数的可选指针,该整数包含addr参数指向的结构的长度。

返回值
如果成功, 则accept函数将返回SOCKET类型的值,该值是新套接字的描述符(实际建立连接的套接字的句柄);否则,将返回INVALID_SOCKET的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。
(addrlen引用的整数最初包含addr指向的空间量。返回时,它将包含返回地址的实际长度(以字节为单位))。

(7)send() 发送数据

1
2
3
4
5
6
int WSAAPI send(
SOCKET s,
const char *buf,
int len,
int flags
);

参量
s:标识已连接套接字的描述符。
buf:指向包含要传输的数据的缓冲区的指针。
len:buf参数指向的缓冲区中数据的长度(以字节为单位)。
flags:一组标志,指定进行呼叫的方式。通过将按位或运算符与以下任何值一起使用来构造此参数。

返回值
如果成功,则send函数返回已发送的字节总数, 该总数可以小于len参数中请求发送的字节数;否则,返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(8)recv() 接收数据

1
2
3
4
5
6
int recv(
SOCKET s,
char *buf,
int len,
int flags
);

参量
s:标识已连接套接字的描述符。
buf:指向缓冲区以接收传入数据的指针。
len:buf参数指向的缓冲区的长度(以字节为单位)。
flags:一组影响此功能行为的标志。

返回值
如果成功,则recv函数返回接收到的字节数,并且buf参数指向的缓冲区将包含接收到的该数据;如果已正常关闭连接,则返回值为零;否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(9)closesocket() 关闭套接字

1
2
3
int closesocket(
IN SOCKET s
);

参量
s:标识要关闭的套接字的描述符。

返回值
如果成功,则closesocket返回零;否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(10)WSACleanup() 关闭WinSock

1
int WSACleanup();

参量
无参量

返回值
如果操作成功,则返回值为零;否则,将返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError函数来检索特定的错误号 。

在多线程环境中, WSACleanup终止所有线程的Windows套接字操作。

3. 服务器端代码(TCP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNNINGS

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

#define SERVER_IP_ADDR "172.25.41.53" //服务器IP地址
#define SERVER_PORT 2450 //服务器端口号
#define BUF_SIZE 1024
#define BACKLOG 10

int main(int argc, char* argv[]) {
int rval, Length = 0;
char sendbuf[BUF_SIZE];
char revbuf[BUF_SIZE];

WORD wVersionrequested;
WSADATA wsaData;
SOCKET ServerSock, MessageSock;
struct sockaddr_in ServerAddr, ClientAddr;

printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n");

/* 加载Winsock */
wVersionrequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionrequested, &wsaData) != 0) {
printf("Failed to load Winsock!\n");
system("pause");
return -1;
}
printf("Succeed to load Winsock!\n");

/* 创建套接字 */
ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ServerSock == INVALID_SOCKET) {
printf("Failed to create socket!\n");
system("pause");
exit(1);
}
printf("Succeed to create socket!\n");

/* 配置服务器IP、端口信息 */
memset(&ServerAddr, 0, sizeof(struct sockaddr)); //每一个字节都用0来填充
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(SERVER_PORT);
ServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);

/* 绑定 */
rval = bind(ServerSock, (SOCKADDR*)& ServerAddr, sizeof(struct sockaddr));
if (rval == SOCKET_ERROR) {
printf("Failed to bind stream socket!\n");
system("pause");
exit(1);
}
printf("Succeed to bind stream socket!\n");

/* 启动监听 */
rval = listen(ServerSock, BACKLOG);
if (rval == SOCKET_ERROR) {
printf("Failed to listen socket!\n");
system("pause");
exit(1);
}
printf("Listening the socket ... ...\n");

/* 接受客户端请求建立连接 */
Length = sizeof(struct sockaddr);
MessageSock = accept(ServerSock, (SOCKADDR*)& ClientAddr, &Length);
if (MessageSock == INVALID_SOCKET) {
printf("Failed to accept connection from client!\n");
system("pause");
exit(1);
}
printf("Succeed to accept connection from client!\n");

printf("\nNow you can have a task!\n");
printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n\n");

/* 发送响应消息到客户端 */
rval = send(MessageSock, "Hello! This is server.", 22, 0);
if (rval <= 0) {
printf("Failed to send the first message!\n");
system("pause");
exit(1);
}
printf("--> Hello! This is server.\n");

while (TRUE) {
/* 接收客户端数据 */
memset(revbuf, 0, BUF_SIZE); //每一个字节都用0来填充
rval = recv(MessageSock, revbuf, BUF_SIZE, 0);
revbuf[rval] = 0x00;
if (rval <= 0)
printf("Failed to receive message from client!\n");
if (rval > 0)
printf("-->[%s:%d] %s\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port), revbuf);
//输出服务器IP地址、端口号、数据内容

/* 输入要发送的数据 */
memset(sendbuf, 0, BUF_SIZE); //每一个字节都用0来填充
printf("--> ");
gets_s(sendbuf); //使用gets_s()能够获取空格

/* 发送数据到客户端 */
rval = send(MessageSock, sendbuf, strlen(sendbuf), 0);
if (rval <= 0) {
printf("Write error or failed to send the message!\n");
system("pause");
exit(1);
}
}

closesocket(MessageSock);
closesocket(ServerSock); //关闭套接字
WSACleanup(); //停止Winsock

system("pause");
return 0;
}

4. 客户端代码(TCP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNNINGS

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

#define SERVER_IP_ADDR "172.25.41.53" //服务器IP地址
#define SERVER_PORT 2450 //服务器端口号
#define BUF_SIZE 1024

int main(int argc, char* argv[]) {
int rval = 0;
char sendbuf[BUF_SIZE];
char revbuf[BUF_SIZE];

WORD wVersionrequested;
WSADATA wsaData;
SOCKET ClientSock;
struct sockaddr_in ServerAddr;

printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n");

/* 加载Winsock */
wVersionrequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionrequested, &wsaData) != 0) {
printf("Failed to load Winsock!\n");
system("pause");
return -1;
}
printf("Succeed to load Winsock!\n");

/* 创建套接字 */
ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ClientSock == INVALID_SOCKET) {
printf("Failed to create socket!\n");
system("pause");
exit(1);
}
printf("Succeed to create socket!\n");

/* 配置服务器IP、端口信息 */
memset(&ServerAddr, 0, sizeof(struct sockaddr)); //每一个字节都用0来填充
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(SERVER_PORT);
ServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);

/* 向服务器发起请求建立连接 */
rval = connect(ClientSock, (SOCKADDR*)& ServerAddr, sizeof(struct sockaddr));
if (rval == SOCKET_ERROR) {
printf("Failed to create connection with server!\n");
system("pause");
exit(1);
}
printf("Succeed to create connection with server!\n");

printf("\nNow you can have a task!\n");
printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n\n");

while (TRUE) {
/* 接收服务器数据 */
memset(revbuf, 0, BUF_SIZE); //每一个字节都用0来填充
rval = recv(ClientSock, revbuf, BUF_SIZE, 0);
revbuf[rval] = 0x00;
if (rval <= 0)
printf("Failed to receive the message from server!\n");
if (rval > 0)
printf("-->[%s:%d] %s\n", inet_ntoa(ServerAddr.sin_addr), ntohs(ServerAddr.sin_port), revbuf);
//输出服务器IP地址、端口号、数据内容

/* 输入要发送的数据 */
memset(sendbuf, 0, BUF_SIZE);
printf("--> ");
gets_s(sendbuf); //使用gets_s()能够获取空格

/* 发送数据到服务器 */
rval = send(ClientSock, sendbuf, strlen(sendbuf), 0);
if (rval <= 0) {
printf("Write error or failed to send the message!\n");
system("pause");
exit(1);
}
}

closesocket(ClientSock); //关闭套接字
WSACleanup; //停止Winsock

system("pause");
return 0;
}

四. 基于UDP协议实现

1. 实现流程

基于UDP实现流程
相比于基于TCP实现,基于UDP实现时由于UDP的“无连接”特点,不再需要服务器端进行listen()和accept()过程,也无需客户端进行connect()过程。同时,WinSock编程中的发送和接收数据函数变为sendto()和recvfrom()。

2. WinSock函数分析

(1)WSAStartup() 初始化WinSock

1
2
3
4
int WSAStartup(
WORD wVersionRequired,
LPWSADATA lpWSAData
);

参量
wVersionRequired:标识所调用WinSock的版本号,使用MAKEWORD函数来生成。
lpWSAData:指向WSADATA数据结构的指针,该数据结构将接收Windows套接字实现的详细信息。

返回值
如果成功,则WSAStartup函数将返回零;否则,返回错误代码(不需要调用WSAGetLastError函数并且不应该调用它来获取错误代码。)

(2)socket() 创建套接字

1
2
3
4
5
SOCKET WSAAPI socket(
int af,
int type,
int protocol
);

参量
af:地址族规范。AF_INET是IPv4的Internet地址族格式。
type:套接字类型规范。SOCK_STREAM类型可将传输控制协议(TCP)用于Internet地址系列。
SOCK_DGRAM类型可将用户数据报协议(UDP)用于Internet地址系列。
protocol:使用的协议。IPPROTO_TCP表示传输控制协议(TCP)。
IPPROTO_UDP表示用户数据报协议(UDP)。
注意:protocol参数的可能选项决定于指定的地址系列和套接字类型。

返回值
如果成功,则socket函数返回新套接字的描述符;否则,返回INVALID_SOCKET的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(3)bind() 绑定

1
2
3
4
5
int bind(
SOCKET s,
const sockaddr *addr,
int namelen
);

参量
s:标识未绑定套接字的描述符。
addr:指向要分配给绑定套接字的本地地址的sockaddr结构的指针。
namelen:name参数所指向的值的长度(以字节为单位)。

返回值
如果成功,则 bind函数返回零;否则,返回SOCKET_ERROR,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(4)sendto() 发送数据

1
2
3
4
5
6
7
8
int WSAAPI send(
SOCKET s,
const char *buf,
int len,
int flags
const sockaddr *to
int tolen
);

参量
s:标识一个套接字的描述符。
buf:指向包含要传输的数据的缓冲区的指针。
len:buf参数指向的缓冲区中数据的长度(以字节为单位)。
flags:一组标志,指定进行呼叫的方式。通过将按位或运算符与以下任何值一起使用来构造此参数。
to:数据发送的目的套接字地址。
tolen:地址长度。

返回值
如果成功,则sendto函数返回已发送的字节总数;否则,返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(5)recvfrom() 接收数据

1
2
3
4
5
6
7
8
int recv(
SOCKET s,
const char *buf,
int len,
int flags
const sockaddr *from
int fromlen
);

参量
s:标识已连接套接字的描述符。
buf:指向缓冲区以接收传入数据的指针。
len:buf参数指向的缓冲区的长度(以字节为单位)。
flags:一组影响此功能行为的标志。
from:捕获数据发送的原地址。
fromlen:地址长度。

返回值
如果成功,则recvfrom函数返回接收到的字节数,并且buf参数指向的缓冲区将包含接收到的该数据;否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(6)closesocket() 关闭套接字

1
2
3
int closesocket(
IN SOCKET s
);

参量
s:标识要关闭的套接字的描述符。

返回值
如果成功,则closesocket返回零;否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError函数来检索特定的错误代码 。

(7)WSACleanup() 关闭WinSock

1
int WSACleanup();

参量
无参量

返回值
如果操作成功,则返回值为零;否则,将返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError函数来检索特定的错误号 。

在多线程环境中, WSACleanup终止所有线程的Windows套接字操作。

3. 服务器端代码(UDP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNNINGS

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

#define SERVER_IP_ADDR "172.25.41.53" //服务器IP地址
#define SERVER_PORT 2450 //服务器端口号
#define BUF_SIZE 1024

int main(int argc, char* argv[]) {
int rval, Length = 0;
char sendbuf[BUF_SIZE];
char revbuf[BUF_SIZE];

WORD wVersionrequested;
WSADATA wsaData;
SOCKET ServerSock;
struct sockaddr_in ServerAddr, ClientAddr;

printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n");

/* 加载Winsock */
wVersionrequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionrequested, &wsaData) != 0) {
printf("Failed to load Winsock!\n");
system("pause");
return -1;
}
printf("Succeed to load Winsock!\n");

/* 创建套接字 */
ServerSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ServerSock == INVALID_SOCKET) {
printf("Failed to create socket!\n");
system("pause");
exit(1);
}
printf("Succeed to create socket!\n");

/* 配置服务器IP、端口信息 */
memset(&ServerAddr, 0, sizeof(struct sockaddr)); //每一个字节都用0来填充
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(SERVER_PORT);
ServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);

/* 绑定 */
rval = bind(ServerSock, (SOCKADDR*)& ServerAddr, sizeof(struct sockaddr));
if (rval == SOCKET_ERROR) {
printf("Failed to bind stream socket!\n");
system("pause");
exit(1);
}
printf("Succeed to bind stream socket!\n");

printf("\nNow you can have a task!\n");
printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n");
printf("Waiting for data from client ... ... \n\n");

while (TRUE) {
Length = sizeof(struct sockaddr);

/* 接收客户端数据 */
memset(revbuf, 0, BUF_SIZE); //每一个字节都用0来填充
rval = recvfrom(ServerSock, revbuf, BUF_SIZE, 0, (SOCKADDR*)& ClientAddr, &Length);
revbuf[rval] = 0x00;
if (rval <= 0)
printf("Failed to receive message from client!\n");
if (rval > 0)
printf("-->[%s:%d] %s\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port), revbuf);
//输出服务器IP地址、端口号、数据内容

/* 输入要发送的数据 */
memset(sendbuf, 0, BUF_SIZE); //每一个字节都用0来填充
printf("--> ");
gets_s(sendbuf); //使用gets_s()能够获取空格

/* 发送数据到客户端 */
rval = sendto(ServerSock, sendbuf, strlen(sendbuf), 0, (SOCKADDR*)& ClientAddr, Length);
if (rval <= 0) {
printf("Write error or failed to send the message!\n");
system("pause");
exit(1);
}
}

closesocket(ServerSock); //关闭套接字
WSACleanup(); //停止Winsock

system("pause");
return 0;
}

4. 客户端代码(UDP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNNINGS

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

#define SERVER_IP_ADDR "172.25.41.53" //服务器IP地址
#define SERVER_PORT 2450 //服务器端口号
#define BUF_SIZE 1024

int main(int argc, char* argv[]) {
int rval, Length = 0;
char sendbuf[BUF_SIZE];
char revbuf[BUF_SIZE];

WORD wVersionrequested;
WSADATA wsaData;
SOCKET ClientSock;
struct sockaddr_in ServerAddr;

printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n");

/* 加载Winsock */
wVersionrequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionrequested, &wsaData) != 0) {
printf("Failed to load Winsock!\n");
system("pause");
return -1;
}
printf("Succeed to load Winsock!\n");

/* 创建套接字 */
ClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ClientSock == INVALID_SOCKET) {
printf("Failed to create socket!\n");
system("pause");
exit(1);
}
printf("Succeed to create socket!\n");

/* 配置服务器IP、端口信息 */
memset(&ServerAddr, 0, sizeof(struct sockaddr)); //每一个字节都用0来填充
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(SERVER_PORT);
ServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);

printf("\nNow you can have a task!\n");
printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * \n");
printf("You can send data to server now: \n\n");

while (TRUE) {
Length = sizeof(struct sockaddr);

/* 输入要发送的数据 */
memset(sendbuf, 0, BUF_SIZE);
printf("--> ");
gets_s(sendbuf); //使用gets_s()能够获取空格

/* 发送数据到服务器 */
rval = sendto(ClientSock, sendbuf, strlen(sendbuf), 0, (SOCKADDR*)& ServerAddr, Length);
if (rval <= 0) {
printf("Write error or failed to send the message!\n");
system("pause");
exit(1);
}

/* 接收服务器数据 */
memset(revbuf, 0, BUF_SIZE); //每一个字节都用0来填充
rval = recvfrom(ClientSock, revbuf, BUF_SIZE, 0, (SOCKADDR*)& ServerAddr, &Length);
revbuf[rval] = 0x00;
if (rval <= 0)
printf("Failed to receive the message from server!\n");
if (rval > 0)
printf("-->[%s:%d] %s\n", inet_ntoa(ServerAddr.sin_addr), ntohs(ServerAddr.sin_port), revbuf);
//输出服务器IP地址、端口号、数据内容
}

closesocket(ClientSock); //关闭套接字
WSACleanup; //停止Winsock

system("pause");
return 0;
}

五、运行结果和相关问题分析

1. 基于TCP运行结果

服务器端:
TCP Server
客户端:
TCP Client

2. 基于UDP运行结果

服务器端:
UDP Server
客户端:
UDP Client

3. 相关分析

  1. 基于TCP时,必须先运行服务器端程序。因为必须首先建立客户端与服务器间的连接之后才能互相收发数据。服务器端没有先前运行,无法监听到客户端发起的连接请求,从而无法响应客户端的连接请求,导致建立连接失败。
    tcp fail

  2. 基于UDP时,如果先运行客户端程序会出现如图所示的现象:客户端程序能够正常运行,并且能够等待用户输入、发送数据给服务器端,但由于服务器端尚未运行,无法接收到来自服务器端的数据。原因是基于UDP的WinSock不用建立客户端与服务器间的连接就能互相收发数据。客户端和服务器端之间直接通过IP和Port来向目标的IP和Port发送数据,无需建立连接,故客户端不用发起连接请求,服务器端也不用监听和接受连接请求。
    udp fail

评论区 (输入正确的邮箱可以收到回复哦!网址可选)