《TCP/IP网络编程》(第一章)理解网络编程和套接字(Windowns平台实现一个Server和client连接)

发布于:2024-05-14 ⋅ 阅读:(152) ⋅ 点赞:(0)

前言:

因为要进行网络编程,我这里使用的IDE是VS Code,所以要对配置进行一点修改,打开tasks.json,添加"-lwsock32"

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "shell",
            "label": "task g++",   
            "command": "D:\\mingw64\\bin\\g++.exe",
            "args": [
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe",
                "-lwsock32"//添加到这里
            ],
            "options": {
                "cwd": "D:\\mingw64\\bin"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build"
        }
    ]
}

1.套接字概念socket

这个翻译咋一看不明所以,其实还是蛮形象的,下面是套接管的图片
在这里插入图片描述
可以通过这个套接管,把两个管子连接在一起。
同样的,套接字就是把计算机和因特网连接起来的工具,所以网络编程也叫做套接字编程

2.套接字编程

①服务器端套接字基本编程步骤如下:
创建套接字——绑定套接字——监听连接——接受连接——通信——关闭
②客户端套接字基本编程步骤如下:
创建套接字——连接服务器——发送请求——接受响应——关闭

1.创建套接字: 调用socket函数创建套接字

SOCKET socket(int af,int type,int prorocol);
//af (Address Family):地址族,指定了套接字使用的通信协议。
//type (Socket Type): 套接字类型,指定了套接字的通信语义。
//protocol (Protocol):指定使用的特定协议。

2.绑定套接字: 调用bind函数分配IP地址和端口号

int bind(SOCKET s,const struct sockaddr* name,int namelen);
//SOCKET s:这是由 socket() 函数创建的套接字。
//name:这是一个指向 sockaddr 结构体的指针,该结构体包含了套接字的地址信息。
//namelen:这是 name 参数指向的 sockaddr 结构体的大小,以字节为单位。

3.监听连接: 调用listen函数转为可接受请求的状态,监听连接请求

int listen(SOCKET s,int backlog);
//SOCKET s:这是由 socket() 函数创建并由 bind() 函数绑定到特定地址的套接字
//backlog:这是一个指定同时可以有多少个未被接受的连接请求在队列中等待的整数值。

4.接受连接: 调用accept函数受理连接请求

SOCKET accept(SOCKET s,const struct sockaddr* name,int namelen);
//SOCKET s:这是监听连接请求的套接字。
//name:这是一个指向 sockaddr 结构体的指针,该结构体包含用于接收连接客户端的地址信息。
//namelen:这是一个指向整数的指针,该整数表示 name 指向的 sockaddr 结构体的初始大小。

5.连接服务器: 使用connect函数将客户端套接字连接到服务器的套接字地址。

connect(SOCKET s,const struct sockaddr* name,int namelen);
//SOCKET s:这是由 socket() 函数创建的套接字。
//name:这是一个指向 sockaddr 结构体的指针,该结构体包含了服务器的地址信息。
//namelen:这是 name 参数指向的 sockaddr 结构体的大小,以字节为单位。

6.发送请求: 一旦连接建立,就可以使用send函数发送数据到服务器

send(SOCKET s, const char* buf, int len, int flags);
//SOCKET s:这是由 socket() 函数创建的套接字。
//buf:这是一个指向数据缓冲区的指针,包含了要发送的数据。
//len:这是 buf 指向的缓冲区的大小,即要发送的数据的字节数。
//flags:这是一个选项标志,用于修改 send() 函数的行为。

7.接受响应

recv(SOCKET s, const char* buf, int len, int flags);
//SOCKET s:这是由 socket() 函数创建的套接字。
//buf:这是一个指向内存缓冲区的指针,用于存储接收到的数据。
//len:这是 buf 指向的缓冲区的大小,即你可以接收的数据的最大字节数。
//flags:这是一个选项标志,用于修改 recv() 函数的行为。

8.关闭套接字

int closesocket(SOCKET s);
//SOCKET s:这是由 socket() 函数创建的套接字。

3.编写第一个Server和client

服务器端套接字代码:

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")// 指定链接到winsock库
void error_handling(char* message);
int main(int argc, const char* argv[])
{
	WSADATA wsaData;// Windows Sockets API需要的数据结构
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAddr, clntAddr;// 用于存储服务器和客户端的地址信息
	int szClntAddr;// 客户端地址结构的大小

	char message[] = "Hello TCP/IP!";// 设置发送给客户端的消息
	if (argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	// 初始化Windows Sockets服务
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		error_handling("WSAStartup() error!");

	//创建套接字	
	hServSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hServSock == INVALID_SOCKET)
		error_handling("socket() error");
		
	memset(&servAddr, 0, sizeof(servAddr));// 清空servAddr结构,避免随机数影响
	servAddr.sin_family = AF_INET;// 设置地址族为IPv4
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置监听地址为任意(服务器的所有接口)
	servAddr.sin_port = htons(atoi(argv[1]));// 设置监听端口为命令行参数指定的端口

	// 绑定套接字,分配IP地址和端口号
	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		error_handling("bind() error");

	// 开始监听连接
	if (listen(hServSock, 5) == SOCKET_ERROR)
		error_handling("listen() error");
		
	szClntAddr = sizeof(clntAddr);// 设置客户端地址结构的大小

	//接受一个客户端连接
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
	if (hClntSock == INVALID_SOCKET)
		error_handling("accept() error");

	// 向客户端发送消息	
	send(hClntSock, message, sizeof(message), 0);
	closesocket(hClntSock);// 关闭客户端socket
	closesocket(hServSock);// 关闭服务器socket
	WSACleanup();// 清理Windows Sockets服务
	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

客户端套接字代码:

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")// 指定链接到winsock库


void ErrorHandling(const char *message);

int main(int argc, char *argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;// 用于存储服务器的地址信息
	char message[30];// 用于接收从服务器发送的消息
	int strLen;// 接收的消息长度

	if (argc != 3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

    // 初始化Windows Sockets服务
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

    //创建套接字
    hSocket = socket(PF_INET, SOCK_STREAM, 0);
    if (hSocket == INVALID_SOCKET)
		ErrorHandling("socket() error");
        
	memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;// 设置地址族为IPv4
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);// 设置服务器的IP地址
    servAddr.sin_port = htons(atoi(argv[2]));// 设置服务器的端口号

    // 尝试连接到服务器
    if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		ErrorHandling("connect() error!");
    
    //接受响应
    strLen= recv(hSocket, message, sizeof(message)-1, 0);
    if (strLen == -1)
		ErrorHandling("read() error!");
        
    printf("Message from server: %s \n", message);
    closesocket(hSocket);// 关闭套接字
    WSACleanup();// 清理Windows Sockets服务
    return 0;
}

void ErrorHandling(const char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

4.运行代码

1.把cpp文件编译为exe
在这里插入图片描述
2.打开两个终端运行文件,我这里直接用VS code。
首先在一个终端运行Serv.exe 8080,打开服务器
在这里插入图片描述
在另一个终端运行Cln.exe 127.0.0.1 8080,即完成了一次客户端服务器的连接
在这里插入图片描述