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

前言:

因为要进行网络编程,我这里使用的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,即完成了一次客户端服务器的连接
在这里插入图片描述

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
当然可以实现一个简单的网络编程,下面是一个使用Winsock API实现的简单的服务器和客户端示例代码,可以在Windows下运行: 服务器端代码: ```c++ #include <iostream> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") //链ws2_32.lib库 using namespace std; int main() { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) //初始化Winsock库 { cout << "WSAStartup failed!" << endl; return 1; } SOCKET serverSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套 if (serverSock == INVALID_SOCKET) { cout << "socket failed!" << endl; WSACleanup(); //释放Winsock库资源 return 1; } sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr.sin_port = htons(12345); if (bind(serverSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) //绑定套 { cout << "bind failed!" << endl; closesocket(serverSock); //关闭套 WSACleanup(); //释放Winsock库资源 return 1; } if (listen(serverSock, SOMAXCONN) == SOCKET_ERROR) //监听套 { cout << "listen failed!" << endl; closesocket(serverSock); //关闭套 WSACleanup(); //释放Winsock库资源 return 1; } cout << "Server is listening..." << endl; SOCKET clientSock = accept(serverSock, NULL, NULL); //受客户端连接 if (clientSock == INVALID_SOCKET) { cout << "accept failed!" << endl; closesocket(serverSock); //关闭套 WSACleanup(); //释放Winsock库资源 return 1; } cout << "Client is connected." << endl; char buf[1024]; int len; while (true) { len = recv(clientSock, buf, sizeof(buf), 0); //收客户端发送的数据 if (len > 0) { buf[len] = '\0'; cout << "Received: " << buf << endl; send(clientSock, buf, len, 0); //向客户端发送数据 } else if (len == 0) { cout << "Client disconnected." << endl; break; } else { cout << "recv failed!" << endl; break; } } closesocket(clientSock); //关闭套 closesocket(serverSock); //关闭套 WSACleanup(); //释放Winsock库资源 return 0; } ``` 客户端代码: ```c++ #include <iostream> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") //链ws2_32.lib库 using namespace std; int main() { WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) //初始化Winsock库 { cout << "WSAStartup failed!" << endl; return 1; } SOCKET clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套 if (clientSock == INVALID_SOCKET) { cout << "socket failed!" << endl; WSACleanup(); //释放Winsock库资源 return 1; } sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //连接服务器IP地址 serverAddr.sin_port = htons(12345); //连接服务器端口号 if (connect(clientSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) //连接服务器 { cout << "connect failed!" << endl; closesocket(clientSock); //关闭套 WSACleanup(); //释放Winsock库资源 return 1; } cout << "Connected to server." << endl; char buf[1024]; int len; while (true) { cout << "Input message: "; cin.getline(buf, sizeof(buf)); if (strcmp(buf, "exit") == 0) //输入exit退出程序 { break; } len = send(clientSock, buf, strlen(buf), 0); //向服务器发送数据 if (len == SOCKET_ERROR) { cout << "send failed!" << endl; break; } len = recv(clientSock, buf, sizeof(buf), 0); //收服务器发送的数据 if (len > 0) { buf[len] = '\0'; cout << "Received: " << buf << endl; } else if (len == 0) { cout << "Server disconnected." << endl; break; } else { cout << "recv failed!" << endl; break; } } closesocket(clientSock); //关闭套 WSACleanup(); //释放Winsock库资源 return 0; } ``` 这个例子中,服务器监听12345端口,客户端连接服务器后可以向服务器发送数据,服务器收到数据后原样回复。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青石横刀策马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值