计网实验——套接字程序设计

计网实验——套接字程序设计

遇到的问题

问题一:

inet_addr在高版本的VS中不能使用

网络上给出解决方法是:

1
项目----项目属性----c/c++----常规----SDL检查改为“否”

但是发现还是不行,另外的解决方法是:

1
项目----项目属性----c/c++----常规----将SDL检查后面的项完全删去

问题二:

ctime: This function or variable may be unsafe

解决方法:(貌似,其实问题一解决了,问题二也就不存在了)

1
属性————预处理器————预处理器定义————添加“_CRT_SECURE_NO_WARNINGS

image-20210311202014450

image-20210311202043133

问题三:

**字符串与char*的冲突**

1
char* p = "abc";  // valid in C, invalid in C++

解决方法:

1
2
char* p = (char*)"abc";  // OK
char const *p = "abc";  // OK

问题四:

运行客户端程序而不运行服务器程序会出现什么错误

img

Error:10057即,connect 尝试连接时连接不通立即返回

解决方法:

1
2
3
4
5
6
7
8
int ret = connect(sock, (struct sockaddr*)&sin, sizeof(sin));  // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,返回SOCKET_ERROR表示出错,应用程序可通过WSAGetLastError()获取相应错误代码。
/*如果没有成功连接到服务器,则ret == -1*/
if (ret == -1){
printf("没有成功连接到服务器.\n");
closesocket(sock);
WSACleanup();
return 0;
}

问题五:

服务器如何可以退出循环?

当接收到exit消息的时候,服务器与客户端同时关闭。

1
2
3
4
5
if (!strcmp(buf, "exit")) {
(void)closesocket(ssock); // 关闭连接套接字
printf("服务器已关闭!!!\n");
break;
}

img

前置知识的学习

结构体sockaddr_in

1
2
3
4
5
6
7
8
9
10
11
struct sockaddr_in{

short sin_family; /*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/

unsigned short sin_port; /*端口号 Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字) */

struct in_addr sin_addr; /*IP address in network byte order(Internet address)可以用来表示一个32位的IPv4地址,其字节顺序为网络顺序(network byte ordered),即该无符号整数采用大端字节序*/

unsigned char sin_zero[8]; /*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐 */

}; //总字节数:2 + 2 + 4 + 8 = 16

上述结构体中的结构体in_addr

1
2
3
4
5
6
7
8
9
struct in_addr {
in_addr_t s_addr;/*in_addr_t 一般为 32位的unsigned int,其字节顺序为网络顺序(network byte ordered),即该无符号整数采用大端字节序

其中每8位代表一个IP地址位中的一个数值.

例如192.168.3.144记为0x9003a8c0,其中 c0 为192 ,a8 为 168, 03 为 3 , 90 为 144

打印的时候可以调用inet_ntoa()函数将其转换为char *类型.*/
};

作业实现

客户端:

image-20210312173653068

服务器:

img

TCP程序设计

编写TCP_client_Echo程序

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
/* TCPClient.cpp */

#include <stdlib.h>
#include <stdio.h>
#include <winsock2.h>
#include <string.h>

#define BUFLEN 2000 // 缓冲区大小
#define WSVERS MAKEWORD(2, 0) // 指明版本2.0
#pragma comment(lib,"ws2_32.lib") // 使用winsock 2.0 Llibrary

/*------------------------------------------------------------------------
* main - TCP client for TIME service
*------------------------------------------------------------------------
*/
int main(int argc, char* argv[])
{
char* host = (char*)"127.0.0.1"; /* server IP to connect */
char* service = (char*)"50500"; /* server port to connect */
struct sockaddr_in sin; /* an Internet endpoint address */
char buf[BUFLEN + 1]; /* buffer for one line of text */
SOCKET sock; /* socket descriptor */
int cc; /* recv character count */

WSADATA wsadata;
WSAStartup(WSVERS, &wsadata); //加载winsock library。WSVERS为请求的版本,wsadata返回系统实际支持的最高版本

sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET

memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host); // 设置服务器IP地址(32位)
sin.sin_port = htons((u_short)atoi(service)); // 设置服务器端口号
int ret = connect(sock, (struct sockaddr*)&sin, sizeof(sin)); // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,返回SOCKET_ERROR表示出错,应用程序可通过WSAGetLastError()获取相应错误代码。
/*如果没有成功连接到服务器,则ret == -1*/
if (ret == -1){
printf("没有成功连接到服务器.\n");
closesocket(sock);
WSACleanup();
return 0;
}
printf("输入你要发送的信息:");
fgets(buf, BUFLEN, stdin);
buf[strlen(buf) - 1] = '\0'; // 把换行符替换为空字符
send(sock, buf, strlen(buf) + 1, 0); // 发送信息,信息末尾不带换行符
if (buf[4] == '\0' && !strcmp(buf,"exit"))
{
closesocket(sock);
WSACleanup();
return 0;
} // 用户退出

cc = recv(sock, buf, BUFLEN, 0); // 第二个参数指向缓冲区,第三个参数为缓冲区大小(字节数),第四个参数一般设置为0,返回值:(>0)接收到的字节数,(=0)对方已关闭,(<0)连接出错
if (cc == SOCKET_ERROR) // 出错。其后必须关闭套接字sock
printf("Error: %d.\n", GetLastError());
else if (cc == 0) { // 对方正常关闭
printf("Server closed!", buf);
}
else if (cc > 0) {
buf[cc] = '\0'; // ensure null-termination
printf("\n服务器的echo:\n%s", buf); // 显示所接收的字符串
}
closesocket(sock); // 关闭监听套接字
WSACleanup(); // 卸载winsock library

printf("按回车键继续...");
getchar(); // 等待任意按键
getchar();
}

编写TCP_server_Echo程序

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
/* TCPServer.cpp - main */

#include <stdlib.h>
#include <stdio.h>
#include <winsock2.h>
#include <time.h>
#include "conio.h"

#define WSVERS MAKEWORD(2, 0)
#define BUFLEN 2000 // 缓冲区大小
#pragma comment(lib,"ws2_32.lib") //使用winsock 2.2 library
/*------------------------------------------------------------------------
* main - Iterative TCP server for TIME service
*------------------------------------------------------------------------
*/
void
main(int argc, char* argv[])
/* argc: 命令行参数个数, 例如:C:\> TCPServer 8080
argc=2 argv[0]="TCPServer",argv[1]="8080" */
{
struct sockaddr_in fsin; /* the from address of a client */
SOCKET msock, ssock; /* master & slave sockets */
WSADATA wsadata;
char* service = (char*)"50500"; /*要绑定的本地端口*/
struct sockaddr_in sin; /* an Internet endpoint address */
int alen; /* from-address length 来源的地址长度 */
char* pts; /* pointer to time string 时间字符串指针 */
time_t now; /* current time 时间变量 */
char buf[BUFLEN + 1]; /* buffer for one line of text */

WSAStartup(WSVERS, &wsadata); // 加载winsock library。WSVERS指明请求使用的版本。wsadata返回系统实际支持的最高版本
msock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
// 返回:要监听套接字的描述符或INVALID_SOCKET

memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇(INET-Internet)
sin.sin_addr.s_addr = INADDR_ANY; // 监听所有(接口的)IP地址。事实上就是0.0.0.0,表示的就是任意IP地址
sin.sin_port = htons((u_short)atoi(service)); // 监听的端口号。atoi--把ascii转化为int,htons--主机序到网络序(host to network,s-short 16位)
bind(msock, (struct sockaddr*)&sin, sizeof(sin)); // 绑定监听的IP地址和端口号

listen(msock, 5); // 建立长度为5的连接请求队列,并把到来的连接请求加入队列等待处理。
printf("服务器启动成功!!!!\n\n");
while (!_kbhit()) { // 检测是否有按键,如果没有则进入循环体执行
alen = sizeof(struct sockaddr); // 取到地址结构的长度
ssock = accept(msock, (struct sockaddr*)&fsin, &alen); // 如果在连接请求队列中有连接请求,则接受连接请求并建立连接,返回该连接的套接字,否则,本语句被阻塞直到队列非空。fsin包含客户端IP地址和端口号
int recvlen = recv(ssock, buf, BUFLEN, 0); // 接收信息
buf[recvlen] = '\0';
if (!strcmp(buf, "exit")) {
(void)closesocket(ssock); // 关闭连接套接字
printf("服务器已关闭!!!\n");
break;
}
(void)time(&now); // 取得系统时间
pts = ctime(&now); // 把时间转换为字符串
char temp_buf[BUFLEN + 1]; /* buffer for one line of text */
sprintf(temp_buf, "接收到的信息:%s\n收到的时间:%s\n", buf, pts);
printf("%s\n", temp_buf); //

int cc = send(ssock, temp_buf, strlen(temp_buf), 0); // 第二个参数指向发送缓冲区,第三个参数为要发送的字节数,第四个参数一般置0,返回值:>=0 实际发送的字节数,0 对方正常关闭,SOCKET_ERROR 出错。

(void)closesocket(ssock); // 关闭连接套接字
}
(void)closesocket(msock); // 关闭监听套接字
WSACleanup(); // 卸载winsock library
printf("按回车键继续...");
getchar(); // 等待任意按键
getchar();


}

编写TCP Echo增强程序

只需在原程序的基础上进行稍微的修改:

要求对sockaddr_in有所理解,且掌握%u%hu的输出方法

1
2
3
4
5
6
7
8
9
10
sprintf(temp_buf, "接收到的信息:%s\n收到的时间:%s客户端的IP地址:%u.%u.%u.%u\n客户端的端口号:%hu\n"
, buf
, pts
, fsin.sin_addr.S_un.S_un_b.s_b1
, fsin.sin_addr.S_un.S_un_b.s_b2
, fsin.sin_addr.S_un.S_un_b.s_b3
, fsin.sin_addr.S_un.S_un_b.s_b4
, fsin.sin_port
); //输出所有信息

UDP程序设计

编写UDP_client程序

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
/* UDPClient.cpp */

#include <stdlib.h>
#include <stdio.h>
#include <winsock2.h>
#include <string.h>
#include <time.h>

#define BUFLEN 2000 // 缓冲区大小
#define WSVERS MAKEWORD(2, 2) // 指明版本2.2
#pragma comment(lib,"ws2_32.lib") // 加载winsock 2.2 Llibrary

int
main(int argc, char* argv[])
{
char* host = (char*)"127.0.0.1"; /* server IP to connect */
u_short service = 50500; /* server port to connect */
struct sockaddr_in toAddr; /* an Internet endpoint address */
int toAddrsize = sizeof(toAddr);
char buf[BUFLEN + 1]; /* buffer for one line of text */
SOCKET sock; /* socket descriptor */
int cc; /* recv character count */
char* pts; /* pointer to time string */
time_t now; /* current time */

WSADATA wsadata;
WSAStartup(WSVERS, &wsadata); /* 启动某版本Socket的DLL */

sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

memset(&toAddr, 0, sizeof(toAddr));
toAddr.sin_family = AF_INET;
toAddr.sin_port = htons(service); //atoi:把ascii转化为int. htons:主机序(host)转化为网络序(network), s--short
toAddr.sin_addr.s_addr = inet_addr(host); //如果host为域名,需要先用函数gethostbyname把域名转化为IP地址


printf("输入你要发送的信息:");
fgets(buf, BUFLEN, stdin);
buf[strlen(buf) - 1] = '\0';
cc = sendto(sock, buf, BUFLEN, 0, (SOCKADDR*)&toAddr, sizeof(toAddr));
if (buf[4] == '\0' && !strcmp(buf, "exit"))
{
printf("服务器关闭!!!");
closesocket(sock);
WSACleanup();
return 0;
} // 用户退出
if (cc == SOCKET_ERROR) {
printf("发送失败,错误号:%d\n", WSAGetLastError());
}
else {
printf("发送成功!\r\n");
}
cc = recvfrom(sock, buf, BUFLEN, 0, (SOCKADDR*)&toAddr, &toAddrsize);
buf[cc] = '\0';
printf("%s\n", buf);
closesocket(sock);
WSACleanup(); /* 卸载某版本的DLL */

printf("按任意键退出...");
getchar();

}

编写UDP_server程序

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
/* UDPServer.cpp */

#include <stdlib.h>
#include <stdio.h>
#include <winsock2.h>
#include <string.h>
#include "conio.h"
#include <time.h>


#define BUFLEN 2000
#define WSVERS MAKEWORD(2, 2)
#pragma comment(lib,"ws2_32.lib")

/*------------------------------------------------------------------------
* main - TCP client for DAYTIME service
*------------------------------------------------------------------------
*/
int main(int argc, char* argv[])
{
char* host = (char*)"127.0.0.1"; /* server IP Address to connect */
char* service = (char*)"50500"; /* server port to connect */
struct sockaddr_in sin; /* an Internet endpoint address */
struct sockaddr_in from; /* sender address */
int fromsize = sizeof(from);
char buf[BUFLEN + 1]; /* buffer for one line of text */
SOCKET sock; /* socket descriptor */
int cc; /* recv character count */
char temp_buf[BUFLEN+1];

WSADATA wsadata;
WSAStartup(WSVERS, &wsadata);
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons((u_short)atoi(service));
bind(sock, (struct sockaddr*)&sin, sizeof(sin));

printf("服务器已开启!!!\n\n");

while (!_kbhit()) {
cc = recvfrom(sock, buf, BUFLEN, 0, (SOCKADDR*)&from, &fromsize);
buf[cc] = '\0';
if (buf[4] == '\0' && !strcmp(buf, "exit")) {

printf("服务器关闭!!!");
closesocket(sock);
WSACleanup();
getchar();
return 0;
}

if (cc == SOCKET_ERROR) {
printf("recvfrom() failed; %d\n", WSAGetLastError());
break;
}
else if (cc == 0)
break;
else {
char* pts; /* pointer to time string 时间字符串指针 */
time_t now; /* current time 时间变量 */
(void)time(&now); // 取得系统时间
pts = ctime(&now); // 把时间转换为字符串
sprintf(temp_buf, "接收到的信息:%s\n收到的时间:%s客户端的IP地址:%u.%u.%u.%u\n客户端的端口号:%hu\n"
, buf
, pts
, from.sin_addr.S_un.S_un_b.s_b1
, from.sin_addr.S_un.S_un_b.s_b2
, from.sin_addr.S_un.S_un_b.s_b3
, from.sin_addr.S_un.S_un_b.s_b4
, from.sin_port
); //输出所有信息
printf("%s\n", temp_buf);
cc = sendto(sock, temp_buf, BUFLEN, 0, (SOCKADDR*)&from, sizeof(from));
}
}
closesocket(sock);
WSACleanup();
getchar();
}

计网实验——套接字程序设计
https://wuhlan3.github.io/2021/03/11/计网实验——套接字程序设计/
Author
Wuhlan3
Posted on
March 11, 2021
Licensed under