Window网络编程之简单TCP建立

步骤分解

  • 服务端建立

    步骤说明涉及函数
    建立服务端SOCKETsocket
    绑定服务IP和端口bind
    监听网络端口listen
    等待建立连接accept
    发送数据send
    关闭服务端SOCKETclosesocket
  • 客户端建立

    步骤说明涉及函数
    建立客户端SOCKETsocket
    连接服务器connect
    接收数据recv
    关闭服务端SOCKETclosesocket

搭建SOCKET开发环境

开发环境说明

window下进行SOCKET编程依赖两个库如下所示:

  • 较早的DLL是wsock32.dll,对应的头文件为winsock1.h
  • 最新的DLL是ws2_32.dll,对应的头文件为winsock2.h

基本所有的window操作系统都已经支持了ws2_32.dll,因此可以直接使用这个库无需考虑第一个比较早的库。除了导入头文件之外还需要导入ws2_32.lib链接库,可使用显示导入方式

#pragma comment (lib, "ws2_32.lib")

如果在vs开发环境下也可以在项目属性中添加依赖库,如果使用Clion则在CMakeLists中add_executable语句前增加一条语句

link_libraries(ws2_32)

启动SOCKET说明

SOCKET标准的启动过程如下:

#include <iostream>
#include <WinSock2.h>

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);
    // ......
    WSACleanup();
    return 0;
}

使用MAKEWORD(2, 2)来明确SOCKET的版本号,WinSock规范的最新版本号为2.2版本,wsock32.dll仅支持1.01.1wsock32.dll已经能够很好的支持TCP/IP通信程序的开发,ws2_32.dll主要增加了对其他协议的支持,建议使用最新的2.2版本。

如果当前工程中还需要用到window.h头文件,需要在引入任何头文件之前增加WIN32_LEAN_AND_MEAN宏定义,减少对较早SOCKET版本的依赖,如果能保证window.hWinSock2.h之后引入也可不添加次宏定义。

创建TCP服务器

创建SOCKET

原型

int socket(int af, int type, int protocol);

参数说明:

字段说明参数填写
af地址族,使用IP的类型AF_INET = IPv4,AF_INET6 = IPv6
type数据传输方式SOCK_STREAM = 面向连接,SOCK_DGRAM = 面向连接
protocol传输协议IPPROTO_TCP,IPPTOTO_UDP

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    SOCKET server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    WSACleanup();
    return 0;
}

绑定端口

原型

int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);

参数说明:

字段说明参数填写
sockSOCKET对象使用socket创建的对象后返回的对象
addrSOCKET地址结构体外部创建好并填写,IP、端口等信息
addrlenSOCKET地址结构体长度-

结构体定义:

struct sockaddr_in{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    // ......

     绑定地址和端口 
    SOCKADDR_IN sock_add_in;
    sock_add_in.sin_family = AF_INET;
    sock_add_in.sin_port = htons(9090);
    sock_add_in.sin_addr.S_un.S_addr = INADDR_ANY;
    int bind_result = bind(server_socket, (SOCKADDR*)&sock_add_in, sizeof(SOCKADDR_IN));
    if (bind_result == SOCKET_ERROR) {
        std::cout << "bind error." << std::endl;
    }

    WSACleanup();
    return 0;
}

其中INADDR_ANY表示当前服务器可以接收任意客户端请求,也可填写具体IP地址,地址填写的时候使用inet_addr函数进行转换。端口号的填写也是一样的需要使用htons函数进行转换。

在进行绑定时需要将SOCKADDR_IN强转换成SOCKADDR结构体,这两个结构体的长度是相同的都是16字节,可以简单的认为SOCKADDR是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而SOCKADDR_IN是专门用来保存IPv4地址的结构体。另外还有SOCKADDR_IN6_LH是专门用来保存IPv6地址的结构体。

监听连接

原型

int listen(SOCKET sock, int backlog);

参数说明:

字段说明参数填写
sockSOCKET对象使用socket创建的对象后返回的对象
backlog请求队列的最大长度-

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    // ......

     监听连接 
    int listen_result = listen(server_socket, 5);
    if (listen_result == SOCKET_ERROR) {
        std::cout << "listen error." << std::endl;
    }

    WSACleanup();
    return 0;
}

listen使SOCKET进入监听状态,其中backlog为请求队列的最大长度动监听是指当没有客户端请求时,SOCKET处于睡眠状态,只有当接收到客户端请求时,SOCKET才会被唤醒来响应请求。

当SOCKET正在处理客户端请求时,如果有新的请求进来SOCKET是没法处理,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。

缓冲区的长度(即,能存放多少个客户端请求)可以通过backlog参数指定,可以根据需求来定,并发量小的话可以是10或者20,或者将backlog的值设置为SOMAXCONN由系统来决定请求队列长度,这个值一般比较大可能是几百或者更多。

当请求队列满时,就不再接收新的请求,客户端会收到WSAECONNREFUSED错误。

连接客户端

原型

SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);

参数说明:

字段说明参数填写
sockSOCKET对象使用socket创建的对象后返回的对象
addr客户端SOCKET地址结构体空结构体在接收连接请求的时候填写填充客户端信息
addrlen客户端SOCKET地址结构体客户端SOCKET地址结构体的长度

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    // ......

     处理连接 
    SOCKADDR_IN client_add_in = { 0 };
    int client_lent = sizeof(SOCKADDR_IN);
    SOCKET client_socket = INVALID_SOCKET;
    client_socket = accept(server_socket, (SOCKADDR*)&client_add_in, &client_lent);
    if (client_socket == INVALID_SOCKET) {
        std::cout << "client connect error." << std::endl;
    }

    WSACleanup();
    return 0;
}

listen只是让SOCKET进入监听状态并没有真正接收客户端请求,listen后面的代码会继续执行,accept才是处理请求的函数,accept会阻塞当前线程执行。

accept返回一个新的SOCKET来和客户端通信,其中SOCKADDR_IN结构会被填充,描述了客户端的IP地址端口号

向客户端发送数据

原型

int send(SOCKET sock, const char * buf, int len, int flags);

参数说明:

字段说明参数填写
sockSOCKET对象使用accept后返回的SOCKET对象
buf发送数据包缓存地址发送的实际数据地址
len发送的数据的字节数-
flags为发送数据时的选项一般填0

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    // .....
    
     发送数据 
    char message[] = "Build TCP Connect.";
    send(client_socket, message, strnlen_s(message, 100), 0);

    WSACleanup();
    return 0;
}

关闭服务器

原型

closesocket(SOCKET s);

参数说明:

字段说明参数填写
sSOCKET对象使用socket创建的对象后返回的对象

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    // ......

     关闭服务器 
    closesocket(server_socket);

    WSACleanup();
    return 0;
}

创建TCP客户端

创建SOCKET

客户端创建SOCKET的步骤与服务器创建SOCKET步骤一致。

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    WSACleanup();
    return 0;
}

连接服务器

原型

int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);

参数和bind一样,不同的是客户端连接的时候需要指定服务器的具体IP。

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    // ......

     连接服务器 
    SOCKADDR_IN sock_add_in;
    sock_add_in.sin_family = AF_INET;
    sock_add_in.sin_port = htons(9090);
    sock_add_in.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    int connect_result = connect(client_socket, (sockaddr*)&sock_add_in, sizeof(SOCKADDR_IN));
    if (connect_result == SOCKET_ERROR) {
        std::cout << "connect error." << std::endl;
    }

    WSACleanup();
    return 0;
}

接收数据

原型

int recv(SOCKET sock, char *buf, int len, int flags);

参数说明:

字段说明参数填写
sockSOCKET对象使用socket创建的SOCKET对象
buf接收数据包缓存地址接收到数据后存放的内存地址
len数据缓存区的大小-
flags为发送数据时的选项一般填0

使用

int main(int argc, char * argv[]) {
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);

    // ......

     接收数据 
    int recv_len = recv(client_socket, message, 256, 0);
    if (recv_len > 0) {
        std::cout << "recv message : %s" << message << std::endl;
    }

    WSACleanup();
    return 0;
}

recv返回实际接收到的数据包的大小,可以根据返回值判断数据是否正确,最后关闭socket即可。

运行结果

TCP连接

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 第1章 Delphi网络编程基础知识 1.1 TCP/IP 1.1.1 TCP/IP结构 1.1.2 应用层协议 1.1.3 传输层协议 1.1.4 网络层协议 1.1.5 RFC和标准简单服务 1.2 TCP/IP基本概念 1.2.1 IP地址 1.2.2 地址解析 1.2.3 域名系统 1.2.4 数据包的封装和分用 1.2.5 端口号 1.3 网络编程接口(Winsock API) 1.4 Winsock常用函数介绍 1.4.1 基本Socket函数 1.4.2 数据库函数 1.4.3 Winsock规范提供的扩展函数 1.5 Delphi Socket网络组件介绍 1.5.1 ClientSocket组件 1.5.2 ServerSocket组件 第2章 基本网络编程实例 2.1 获取IP地址 2.1.1 利用系统工具获得IP地址 2.1.2 使用GetHostByName函数来获取IP 2.1.3 使用WSAAsyncGetHostByName函数获取IP地址 2.1.4 多IP情况的处理 2.1.5 关于IP地址和实际的地址的区别 2.2 获取子网掩码 2.2.1 Windows NT系统中获取子网掩码 2.2.2 Window 9x系统中获取子网掩码 2.3 获取计算机名 2.3.1 获取和设置本机主机名 2.3.2 获取远程主机名称 2.4 网络连接情况检测 2.4.1 使用WinInet高级函数库函数检测网络状态 2.4.2 通过读取系统状态参数检测网络状态 2.5 获取DNS信息 2.5.1 Windows NT系统中获取DNS信息 2.5.2 Windows 9x系统中获取DNS信息 2.6 网卡信息的获取 2.6.1 使用GUID获取网卡地址 2.6.2 NetBIOS来获得MAC地址 2.6.3 使用RPC方式获得MAC地址 第3章 FTP高级编程 3.1 FTP简介 3.2 安装设置FTP服务器 3.3 使用Windows内置FTP程序 3.4 深入FTP协议 3.4.1 FTP命令大全 3.4.2 FTP工作模式 3.5 开发FTP程序的方法 3.6 API开发高级FTP客户端程序 3.6.1 建立工程项目 3.6.2 关键代码分析 3.7 开发FTP服务器 3.7.1 建立工程项目 3.7.2 关键代码分析 第4章 HTTP高级开发 4.1 HTTP协议基本知识 4.1.1 HTTP背景 4.1.2 HTTP的内容 4.1.3 消息(Message) 4.1.4 请求(Request) 4.1.5 响应(Response) 4.1.6 访问认证 4.1.7 URL编码 4.1.8 HTTP协议的应用 4.2 开发文件下载程序 4.2.1 建立工程项目 4.2.2 关键代码分析 4.2.3 技术要点分析 4.3 HTTP API高级开发 4.3.1 控件介绍 4.3.2 关键代码分析 4.3.3 关键技术分析 4.4 Web Server高级开发 4.4.1 Web Server的基本理论 4.4.2 建立工程项目 4.4.3 关键代码分析 4.4.4 Web服务器的扩充 4.5 Web代理服务器的实现 4.5.1 代理服务器介绍 4.5.2 IE中使用代理服务器设置 4.5.3 建立工程项目 4.5.4 关键代码分析 第5章 Telnet高级编程 5.1 Telnet简介 5.2 使用Windows的Telnet程序登录远程服务器 5.3 深入Telnet协议 5.3.1 NVT ASCII字符集 5.3.2 Telnet命令 5.3.3 协商选项 5.3.4 子协商选项 5.3.5 Telnet操作方式 5.4 BBS客户端高级开发 5.4.1 建立工程项目 5.4.2 关键代码分析 5.5 Telnet代理服务程序实现 5.5.1 建立工程项目与关键代码分析 第6章 E-mail协议及高级编程 6.1 SMTP及发送电子邮件 6.1.1 SMTP的模型描述 6.1.2 SMTP的会话过程 6.2 POP3与接收电子邮件 6.2.1 POP3的模型描述 6.2.2 POP3的会话过程 6.3 信件结构详述 6.3.1 RFC822信件的格式和内容 6.3.2 构造符合RFC822的信件 6.3.3 RFC822信件的语法分析 6.4 MIME编码解码与发送附件 6.4.1 RFC822的局限 6.4.2 UUENCODE编码与解码 6.4.3 MIME及其编码 6.4.4 构造MIME信件 6.4.5 MIME信件的语法分析 6.5 E-mail乱码 6.5.1 乱码的常见形式及形成原因 6.5.2 避免乱码的方法 6.6 E-mail程序开发 6.6.1 建立工程项目 6.6.2 关键代码分析 第7章 网络监控、流量统计与资源搜索 7.1 网络流量统计 7.1.1 建立工程项目 7.1.2 关键代码分析 7.2 网络连接监控 7.2.1 建立工程项目 7.2.2 关键代码分析 7.3 网络配置和统计的使用实例 7.3.1 建立工程项目 7.3.2 关键代码分析 7.4 局域网资源搜索 7.4.1 建立工程项目 7.4.2 关键代码分析 7.4.3 关键技术分析 第8章 Modem串口通信编程 8.1 Modem的基础知识 8.1.1 Modem状态 8.1.2 AT命令 8.1.3 S寄存器 8.1.4 Modem返回信息码 8.2 SPComm通信控件 8.2.1 SPComm控件的属性 8.2.2 SPComm控件的方法 8.2.3 SPComm控件的事件 8.3 Windows串口通信编程 8.3.1 Windows通信API和串口通信 8.3.2 打开和关闭串口 8.3.3 串口配置和串口属性 8.3.4 读写串口 8.3.5 通信事件 8.3.6 设备控制命令 8.4 ASCII控制字符 8.5 Modem文件传输应用实例 8.5.1 建立工程项目 8.5.2 关键代码分析 第9章 拨号网络编程 9.1 RAS客户机 9.2 建立拨号连接 9.3 使用RAS(远程访问服务) 9.3.1 用系统电话簿进行拨号 9.3.2 电话簿条目的管理 9.3.3 在程序中创建拨号连接 9.3.4 状态通知 9.4 RAS高级开发拨号程序 9.4.1 创建工程项目 9.4.2 关键代码分析 第10章 传真高级编程 10.1 传真编程的基础知识 10.1.1 T.30传真通信协议 10.1.2 HDLC信息包 10.1.3 传真字段 10.1.4 成串信息包 10.1.5 同步线路控制 10.1.6 传真的五个阶段介绍 10.2 传真Modem的分类 10.2.1 传真分类 10.2.2 一类传真Modem 10.2.3 二类传真Modem 10.3 传真会话实例描述 10.3.1 一类传真的发送实例 10.3.2 一类传真的接收实例 10.3.3 二类传真的发送实例 10.3.4 二类传真的接收实例 10.4 DIS/DCS位映像 10.4.1 向后兼容性和可扩展性 10.4.2 新的 FCF 10.4.3 最小性能集合 10.4.4 DIS/DCS信息包的逐位解释 10.5 T.4传真图像协议 10.5.1 分辨率 10.5.2 文件尺寸 10.6 传真编码 10.6.1 一维编码(改进型哈夫曼编码) 10.6.2 二维编码(READ编码) 10.6.3 编码方式综述 10.6.4 行终码 10.6.5 页编码 10.7 传真高级编程 10.7.1 创建工程项目 10.7.2 关键代码分析
以下是一个使用Qt中的TCP客户端编程实现用户登录的示例: ```python import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton from PyQt5.QtNetwork import QTcpSocket class LoginWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("用户登录") self.resize(300, 200) layout = QVBoxLayout() self.label_username = QLabel("用户名:") self.edit_username = QLineEdit() layout.addWidget(self.label_username) layout.addWidget(self.edit_username) self.label_password = QLabel("密码:") self.edit_password = QLineEdit() self.edit_password.setEchoMode(QLineEdit.Password) layout.addWidget(self.label_password) layout.addWidget(self.edit_password) self.button_login = QPushButton("登录") self.button_login.clicked.connect(self.login) layout.addWidget(self.button_login) self.setLayout(layout) self.tcp_socket = QTcpSocket() self.tcp_socket.connected.connect(self.on_connected) self.tcp_socket.disconnected.connect(self.on_disconnected) self.tcp_socket.readyRead.connect(self.on_ready_read) def login(self): username = self.edit_username.text() password = self.edit_password.text() if username and password: self.tcp_socket.connectToHost("服务器IP地址", 8888) # 发送登录请求数据 self.tcp_socket.write(f"LOGIN|{username}|{password}".encode()) def on_connected(self): print("已连接到服务器") def on_disconnected(self): print("与服务器断开连接") def on_ready_read(self): data = self.tcp_socket.readAll().data().decode() if data == "LOGIN_SUCCESS": print("登录成功") elif data == "LOGIN_FAILED": print("登录失败") if __name__ == "__main__": app = QApplication(sys.argv) login_window = LoginWindow() login_window.show() sys.exit(app.exec_()) ``` 请注意,上述示例中的服务器IP地址和端口号需要根据实际情况进行修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值