14.1 Socket 套接字编程入门
Winsock是Windows操作系统上的套接字API,用于在网络上进行数据通信。套接字通信是一种允许应用程序在计算机网络上进行实时数据交换的技术。通过使用Windows提供的API,应用程序可以创建一个套接字来进行数据通信。这个套接字可以绑定到一个端口,以允许其他应用程序连接它。另外,Winsock可以使用TCP/IP、UDP等协议来完成不同类型的数据传输任务。在网络应用程序开发中,套接字通信可以帮助应用程序开发者实现客户端/服务端模型,并实现数据的可靠传输。
一般套接字通信需要经历,创建套接字(Socket),绑定(Bind),监听(Listen),接受(Accept),连接(Connect),发送数据(Send),接收数据(Receive),关闭(Close)等几个关键步骤,当读者需要使用网络通信时需引入winsock2.h头文件,并通过#pragma comment(lib,"ws2_32.lib")包含对应库,需要注意的是该头文件与windows.h头冲突,如果两者同时存在则会出现编译不通过的情况;
14.1.1 服务端通信
(1)WSAStartup(MAKEWORD(2, 0), &WSAData)
当读者需要使用套接字编程时,不论是服务端还是客户端都需要调用WSAStartup初始化套接字库,该函数接受两个参数传递,第一个参数一般默认会传递MAKEWORD(2, 0) 它是一个宏,用于将两个8位的字节合并成一个16位的字,在MAKEWORD(2, 0)中,括号内的数字分别代表高位字节(2)和低位字节(0),宏会将它们合并成一个16位的无符号short整型数据,即0000001000000000(二进制),表示Winsock的版本号为2.0。第二个参数WSADATA结构体,用于Winsock初始化时存储相关的信息,一般会在全局WSADATA WSAData;直接定义得到。
#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
// 定义结构体
WSADATA WSAData;
// 启动winsock中的WSAStartup()函数对Winsock DLL进行初始化
if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
{
std::cout << "WSA动态库初始化失败" << std::endl;
return 0;
}
(2)socket(AF_INET, SOCK_STREAM, 0)
通信的第二步则是调用Socket()函数,该函数是用于创建一个套接字的系统调用。在该函数中,给定三个参数,分别为地址族(Address Family)、套接字类型(Socket Type)和协议(Protocol),套接字在初始化并完成时会返回一个SOCKET类型的文件描述符句柄,此处我们将该句柄存储至server_socket变量内。AF_INET用于指定套接字地址族为IPv4类型,SOCK_STREAM则用于指定该套接字的类型为流式套接字,用于面向连接的可靠数据传输(TCP协议)。
// 服务进程创建套接字句柄(用于监听)
SOCKET server_socket;
// 调用socket()函数创建一个流套接字,参数(网络地址类型,套接字类型,网络协议)
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == ERROR)
{
std::cout << "Socket 创建失败" << std::endl;
WSACleanup();
return 0;
}
(3)bind(server_socket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr))
套接字编程的第三步则是绑定,套接字的绑定需要调用bind()函数实现,该函数接受三个参数传递,第一个参数是socket()中创建的套接字文件描述符句柄,该参数用于指定针对哪一个套接字进行操作,第二个参数则是sockaddr_in类型的结构体,该结构体内用于指定需要绑定套接字的具体类型参数等信息,在如下代码中我们通过ServerAddr.sin_family = AF_INET;将套接字类型设置为了互联网域模式,通过ServerAddr.sin_port = htons(9999);指定了需要绑定的端口号,而ServerAddr.sin_addr.s_addr = inet_addr("0.0.0.0");则用于指定了要绑定本机的那个网口,一般而言如果读者需要在本机使用此处可填入127.0.0.1而如果侦听任意一个网口则可使用0.0.0.0,第三个参数则是传入结构体的长度,此处通过sizeof(ServerAddr)方法得到,最终将结构体ServerAddr直接填入绑定函数即可实现对网络套接字的绑定。
// 结构sockaddr_in用来标识TCP/IP协议下的地址,可强制转换为sockaddr结构
struct sockaddr_in ServerAddr;
// 字段sin_family必须设为AF_INET,表示该Socket处于Internet域
ServerAddr.sin_family = AF_INET;
// 字段sin_port用于指定服务端口,注意避免冲突
ServerAddr.sin_port = htons(9999);
// 字段sin_addr用于把一个IP地址保存为一个4字节,无符号长整型,根据不同用法还可表示本地或远程IP地址
// 该字段可以直接使用INADDR_ANY代表侦听所有地址,也可指定地址
ServerAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
// 调用bind()函数将本地地址绑定到所创建的套接字上,以在网络上标识该套接字
if (bind(server_socket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
std::cout << "绑定套接字失败" << std::endl;
closesocket(server_socket);
WSACleanup();
return 0;
}
(4)listen(server_socket, 10)
当套接字被绑定后,接下来则是侦听套接字,通过调用listen()函数将套接字置入监听模式并准备接受连接请求,该函数需要传入两个参数,参数1为套接字套接字句柄,参数二为侦听套接字最大连接数,如果进入侦听状态则说明该套接字是等待连接状态,一旦服务器接受了连接,它可以使用返回的套接字对象与发起连接的客户端进行通信。
// 将 ServerAddr.sin_addr 网络字节序,转为本机侦听IP地址
char local_address[20];
inet_ntop(AF_INET, &ServerAddr.sin_addr, local_address, 16);
std::cout << "侦听本地地址: " << local_address << " 侦听本地端口: " << ntohs(ServerAddr.sin_port) << std::endl;
// 参数(已捆绑未连接的套接字描述字,正在等待连接的最大队列长度)
if (listen(server_socket, 10) == SOCKET_ERROR)
{
std::cout << "侦听套接字失败" << std::endl;
closesocket(server_socket);
WSACleanup();
return 0;
}
(5)accept(server_socket, (LPSOCKADDR)0, (int*)0)
当一个套接字进入侦听状态后则下一步是需要等待有客户端连接到本端,当服务器通过调用listen()函数开始监听连接请求时,客户端可以通过使用connect()函数尝试与服务器建立连接。一旦客户端发送连接请求,服务器将收到通知。然后服务器可以使用accept()函数接受连接请求并创建一个新的套接字对象,该对象可以用于与客户端进行通信。
accept() 函数通常在一个循环中使用,以便服务器可以在等待新连接时继续处理已连接的客户端。每次调用accept()函数时,如果有连接请求,则函数将阻塞直到一个连接请求被接受。一旦连接请求被接受,函数将返回一个新的套接字对象和客户端的地址信息。
在接受连接请求并创建新的套接字对象之后,服务器可以使用该对象与客户端进行通信。同时,服务器可以使用原始的server_socket套接字对象来等待更多的连接请求,以便能够接受更多的客户端连接。
如下的代码中当accept()接收到等待消息时,则会将该句柄保存至message_socket变量内,此时用户只需要向该指针中发送recv()或接收send()数据即可,此时套接字通信即可正式被建立起来。
// 数据接收缓冲区
SOCKET message_socket;
char buf[8192] = {0};
while (1)
{
// 进入监听状态后,调用accept()函数接收客户端的连接请求,并把连接传给msgsock套接字
// 原sock套接字继续监听其他客户机连接请求
if ((message_socket = accept(server_socket, (LPSOCKADDR)0, (int*)0)) == INVALID_SOCKET)
{
continue;
}
// 初始化数据接收缓冲区
memset(buf, 0, sizeof(buf));
// 接收客户端发送过来的数据
bool ref = recv(message_socket, buf, 8192, 0);
if (ref != 0)
{
std::cout << "接收数据: " << buf << std::endl;
}
// 关闭子套接字
closesocket(message_socket);
}
至此我们的服务端将被运行起来,需要注意的是服务端程序如果需要结束本次会话则需要手动调用closesocket(server_socket);关闭一个套接字句柄,当整个进程执行结束后读者还需要调用WSACleanup()终止对Winsock DLL的使用,并释放资源。
14.1.2 客户端通信
对于客户端通信而言其流程与服务端通信基本保持一致,该流程分别是,创建套接字,连接到服务器,建立连接,发送数据,关闭连接,对于初始化部分客户端通信与服务端没有任何区别,唯一的区别在于对于服务端而言一般是使用listen()函数侦听套接字,而对于客户端而言则是使用connect()函数连接到服务端,一旦连接建立成功,客户端可以通过向服务器发送数据来与服务器进行通信。
在调用connect(socket_addr)时,需要传递一个参数sockaddr。sockaddr 是一个结构体,包含了客户端与服务器的地址信息,包括其IP地址和端口号。在C/C++中,sockaddr 结构体通常被定义为sockaddr_in结构体,包含了IP地址和端口号等信息。如果连接建立成功,connect() 函数将返回 0。如果连接失败,则会返回一个错误代码,其中最常见的错误是连接超时或目标主机拒绝连接。
一旦连接建立成功,客户端可以使用新创建的套接字对象向服务器发送数据,并使用recv()函数从服务器接收数据。一般来说,在与服务器进行通信之前,客户端套接字需要使用bind()函数指定一个本地地址和端口,以确保数据可以正确地传输。
int main(int argc, char* argv[])
{
char buf[8192] = { 0 };
while (1)
{
std::cout << "发送数据: ";
int inputLen = 0;
memset(buf, 0, sizeof(buf));
// 输入以回车键为结束标识
while ((buf[inputLen++] = getchar()) != '\n'){ ; }
// 初始化
WSADATA WSAData;
if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
{
continue;
}
// 创建套接字
SOCKET client_socket;
if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
WSACleanup();
continue;
}
// 填充通信结构体
struct sockaddr_in ClientAddr;
ClientAddr.sin_family = AF_INET;
ClientAddr.sin_port = htons(9999);
ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 连接到服务端
if (connect(client_socket, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
{
closesocket(client_socket);
WSACleanup();
continue;
}
// 向服务端发送数据
send(client_socket, buf, 8192, 0);
// 关闭套接字
closesocket(client_socket);
WSACleanup();
}
return 0;
}
读者可自行运行上述程序,启动服务端与客户端,并发送测试数据观察变化,当发送数据后读者应该能看到如下图所示的提示信息;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/83cf7258.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
14.1 Socket 套接字编程入门的更多相关文章
- socket套接字编程 HTTP协议
socket套接字编程 套接字介绍 1. 套接字 : 实现网络编程进行数据传输的一种技术手段 2. Python实现套接字编程:import socket 3. 套接字分类 >流式套接 ...
- socket套接字编程(1)——基本函数
TCP交互流程: 服务器:1. 创建socket:2. 绑定socket和端口号:3. 监听端口号:4. 接收来自客户端的连接请求:5. 从socket中读取字符:6. 关闭socket. 客户端:1 ...
- linux网络环境下socket套接字编程(UDP文件传输)
今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...
- linux网络编程-(socket套接字编程UDP传输)
今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...
- socket 套接字编程
今日内容 socket 套接字编程 简易服务端与客户端代码实现 通信循环 黏包现象(TCP协议) 报头制作.struct 模块.封装形式 内容详细 一.socket 套接字编程 实现一款能够进行数据交 ...
- linux 套接字编程入门--Hello World
下述代码是linux套接字编程的入门代码.分为服务端和客户端源码. 服务端代码的主要流程是绑定ip地址和端口号建立套接字,等待客户端发起访问.接受客户端请求之后,向客户端发送字符串"hell ...
- Linux之socket套接字编程20160704
介绍套接字之前,我们先看一下传输层的协议TCP与UDP: TCP协议与UDP协议的区别 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UD ...
- 基于TCP协议的socket套接字编程
目录 一.什么是Scoket 二.套接字发展史及分类 2.1 基于文件类型的套接字家族 2.2 基于网络类型的套接字家族 三.套接字工作流程 3.1 服务端套接字函数 3.2 客户端套接字函数 3.3 ...
- 基于TCP连接的socket套接字编程
基于TCP协议的套接字编程(简单) 服务端 import socket server = socket.socket() server.bind( ('127.0.0.1', 9999) ) serv ...
- day31 socket套接字编程
为什么要有套接字编程? 在上节课的学习中,我们学习了OSI七层协议,但是如果每次进行编程时我们都需要一层一层的将各种协议使用在我们的程序中,这样编写程序实在是太麻烦了,所以为了让程序的编写更加的简单, ...
随机推荐
- 大数据实战手册-开发篇之pycharm远程开发调试
2.1 pycharm远程开发调试 2.1.1 python版本一致 #版本都保持3.6.6 #root cd /usr/local/python3/bin/pip3 list 备注:[python模 ...
- 如何扩展及优化CI/CD流水线?
如今应用程序的开发通常由多个开发人员组成的团队完成.每个人或团队在项目中发挥自己的作用,然后我们发现在项目的末尾总是有几段代码需要编译,根据每个人的工作方法,管理这种集成可能会浪费很多时间.持续集成和 ...
- php处理emoji表情 存数据库
PHP 处理emoji表情 存数据库 直接过滤掉 1 function filter_emoji($str) { 2 $regex = '/(\\\u[ed][0-9a-f]{3})/i'; 3 $s ...
- 2023年icpc大学生程序设计竞赛-crf
第一次在除郑轻以外的校外的地方比赛,也是第一次出市比赛,赛程也比较长.20号出发的时候遇到一些意外,不过无伤大雅,第一天热身赛平平无奇,晚上的时候补了一下前年icpc的题,一个多小时做了五题,很是自信 ...
- Python异常模块与包
Python异常模块与包 一.了解异常 1.1 什么是异常 当检测到一个错误时,Python解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常", 也就是我们常说 ...
- Centos 8 时钟同步
Centos 8 时钟同步使用的是 chrony 参考:https://www.cnblogs.com/my-show-time/p/14658895.html $ rpm -qa | grep ch ...
- 构建易于运维的 AI 训练平台:存储选型与最佳实践
伴随着公司业务的发展,数据量持续增长,存储平台面临新的挑战:大图片的高吞吐.超分辨率场景下数千万小文件的 IOPS 问题.运维复杂等问题.除了这些技术难题,我们基础团队的人员也比较紧张,负责存储层运维 ...
- flink-cdc同步mysql数据到elasticsearch
1,什么是cdc CDC是(Change Data Capture 变更数据获取)的简称.核心思想是,监测并捕获数据库的变动(包括数据 或 数据表的插入INSERT.更新UPDATE.删除DELETE ...
- 古早wp合集
0x00 首先非常感谢大家阅读我的第一篇.本文章不仅仅是题解,一些细枝末节的小问题也欢迎大家一起解答. 小问题的形式如Qx:xxxxxxx? 欢迎发现小问题并讨论~~ N1nE是本人另外一个名字,目前 ...
- 文心一言 VS 讯飞星火 VS chatgpt (81)-- 算法导论7.4 6题
六.如果用go语言,考虑对 PARTITION 过程做这样的修改:从数组 A 中随机选出三个元素,并用这三个元素的中位数(即这三个元素按大小排在中间的值)对数组进行划分.求以a 的函数形式表示的.最坏 ...