
(P99)winsock编程:winsocket相关
发布日期:2021-05-08 18:00:01
浏览次数:26
分类:原创文章
本文共 11071 字,大约阅读时间需要 36 分钟。
文章目录
1.什么是Socket
- 独立于具体协议的网络编程接口
TCP/IP和UNIX域 - 在ISO模型中,主要位于会话层和传输层之间
- BSD Socket(伯克利套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台。
- 不同操作系统中的Socket
Windows Socket (Winsock)
Linux Socket (BSD Socket)
2.Socket类型
- 流式套接字(SOCK_STREAM)
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。 - 数据报套接字(SOCK_DGRAM)
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。 - 原始套接字(SOCK_RAW)
可以对较低层次协议,如IP、ICMP直接访问。
3.Socket的位置
4.WinSock介绍
- Winsock是一个基于Socket模型的API,在Windows系统中广泛使用
- 它在Berkeley接口函数的基础上,还增加了基于消息驱动机制的Windows扩展函数
- Winsock1.1只支持TCP/IP网络,Winsock2.2增加了对更多协议的支持
5.在工程中例用Winsock
- 包含头文件
Winsock2.h
- 导入库ws2_32.lib的方法:
(1)#pragma comment(lib,”ws2_32.lib”);(2)可以打开工程属性页,配置属性->链接器->输入->附加依赖项中加入ws2_32.lib以上两种方法任选其一
(2)
6.基本函数
- 网络连接函数
socket 创建套接字bind 绑定本机端口listen 监听端口accept 接受连接connect 建立连接recv, recvfrom 数据接收send, sendto 数据发送closesocket, shutdown 关闭套接字
- 转换函数
IP地址转换函数inet_addr() 点分十进制数表示的IP地址转换为网络字节序的IP地址inet_ntoa() 网络字节序的IP地址转换为点分十进制数表示的IP地址字节序函数htonl 4字节主机字节序转换为网络字节序ntohl 4字节网络字节序转换为主机字节序htons 2字节主机字节序转换为网络字节序ntohs 2字节网络字节序转换为主机字节序
7.面向连接的C/S程序工作流程图
- win不使用read和write;win需要WSAStartup和WSACleanup;win用的是closesocket而不是close
8.Windows Socket的启动
- 使用Winsock API编制的网络应用程序中,在调用任何一个Winsock函数之前都必须检查协议栈安装情况。
函数原型: int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData );函数参数:wVersionRequested是一个WORD型(双字节型)数值,指定使用的版本号,对Winsock2.2而言,此参数的值为0x0202,也可以用宏MAKEWORD(2,2)来获得lpWSAData是一个指向WSADATA结构的指针,它返回关于Winsock实现的详细信息
- Winsock启动示例
#include <Winsock2.h>WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(2,2);if(WSAStartup(wVersionRequested,&wsaData) != 0){ //Winsock初始化错误 return;}if(wsaData.wVersion != wVersionRequested){ //Winsock版本不匹配 WSACleanup(); return;}//说明WinsockDLL正确加载,可以执行以下代码
- 创建套接口socket()
应用程序在使用套接口通信前,必须要拥有一个套接口。函数原型:SOCKET socket(int af,int type,int protocol);函数参数:af参数:说明套接字接口要使用的协议地址族,地址族与协议族含义相同。如果想建立一个TCP或UDP,只能用常量AF_INET表示使用互联网协议(IP)地址。type参数:描述套接口的类型,af是AF_INET的时候只能为SOCK_STREAM、SOCK_DGRAM或SOCK_RAW
- 创建套接口socket()
函数参数:
protocol:说明该套接口使用的特定协议,当协议地址族af和协议类型type确定后,协议字段可以使用的值是限定的
返回值:成功返回一个整型代表创建成功的socket编号;失败返回INVALID_SOCKET,可以使用WSAGetLastError()获得错误编号。 - 指定本地地址-bind()
函数参数:name是一个与指定协议有关的地址结构指针,存储了套接口的地址信息,Winsock中使用sockaddr_in结构指定IP地址和端口信息 struct sockaddr_in{ short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }sin_family一般为AF_INET,表示使用IP地址族;sin_port是以网络字节序表示的16位端口号;sin_addr是网络字节序的32位IP地址;sin_zero 字段一般不用,用0填充namelen表示地址参数(name)的长度IP地址参数为INADDR_ANY,则由系统内核来自动指定,port为0,则由系统自动指派一个1024~5000之间惟一的端口号
- IP地址转换函数-inet_addr()
把"xxx.xxx.xxx.xxx"的10进制的IP地址转换为32位整数表示方法函数原型:unsigned long inet_addr( const char FAR *cp);函数参数:cp代表IP地址的点格式的字符串。返回值:成功返回用32位整数表示的IP地址(按网络字节排列顺序),失败返回INADDR_NONE。
- 服务器端启动监听-listen()
在一个服务器端用socket()调用成功创建了一个套接口,并用bind()函数和一个指定的地址关联后,就需要指示该套接口进入监听连接请求状态,这需要通过listen()函数来实现函数原型: int listen(SOCKET s,int backlog);函数参数:s代表一个已绑定了地址,但还未建立连接的套接口描述字backlog指定了正在等待连接的最大队列长度返回值:请参考bind()函数
- 客户端请求连接-connect()
当服务器端建立好套接口并与一个本地地址绑定后,就进入监听状态,等待客户发出连接请求。在客户端套接口建立好之后,就调用connect()函数来与服务器建立连接。函数原型: int connect( SOCKET s, const struct sockaddr FAR * name, int namelen);函数参数:s将要建立连接的套接口描述字name是一个指向远端套接口地址结构(sockaddr_in)的指针,表示s套接口欲与其建立一条连接namelen是服务器端的地址长度,即name的长度返回值:请参考bind()函数。
-
connect()函数的说明
(1)在客户端使用该函数请求建立连接时,将激活建立连接的三次握手,用来建立一条到服务器TCP的连接。如果调用该函数前没有调用bind()来绑定本地地址,则由系统隐式绑定一个地址到该套接口
(2)该函数用在UDP的客户端时,connect()函数并不是真正地发出建立请求连接的请求,调用将从本地操作系直接返回。这样可以将服务器的地址信息保存下来,在后续UDP端口发送数据时,由套接口自动在发送函数中填入服务器地址,而不需要由应用程序在调用发送函数时填入 -
服务器端接受连接-accept()
在服务器端通过listen()函数调用表示服务器进入监听客户的连接请求状态,而在服务器端调用accept()函数时表示可以接收来自客户端由connect()发出的连接请求,双方进入连接状态。函数原型: SOCKET accept(SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen);函数参数:s标识一个套接字,该套接口处于监听状态addr是一个地址结构的指针,用来存放发出连接请求的那个客户机的IP地址信息addrlen指出客户套接口地址结构的长度返回值:请参考socket()函数。备注:该函数用于面向连接的服务器端,在IP协议族中,只用于TCP服务器端
- 发送数据-send()
在已经建立连接的套接口上发送数据,可以使用send()函数函数原型:int send( SOCKET s, const char FAR * buf, int len,int flags);函数参数:s用于标识已建立连接的套接字buf是一个字符缓冲区,内有将要发送的数据len即将发送的缓冲区中的字符数flags用于控制数据传输方式,0表示按正常方式发送数据;宏MSG_DONTROUTE说明系统目标主机就在直接连接的本地网络中,无需路由选择;MSG_OOB指出数据是按带外数据发送的备注:send()函数适用于已建立连接的数据报或流式套接口发送数据,对于数据报类型套接口必须注意发送数据长度不大于通信子网的IP包最大长度
- 接收数据-recv()函数
对于已建立连接的套接口来说,要从套接口上接收数据,就要使用recv()函数。函数原型:int recv(SOCKET s,char FAR * buf, int len,int flags);函数参数:s为已建立连接的套接口buf为用于接收数据的缓冲区len为缓冲区的长度flags指定调用的方式。0表示接收的是正常数据,无特殊行为。MSG_PEEK表示会使有用的数据复制到所提供的接收端缓冲区内,但是没有从系统缓冲区中将数据删除。MSG_OOB表示处理带外数据。
- 关闭读写通道-shutdown()
在一个套接口上的读写操作完成后,应该首先使用shutdown()函数来关闭套接口的读通道、写通道或读写通道,这样做的好处是当双方不再有数据要发送或接收时,可以通知对方,以防止数据丢失,并能“优雅”地关闭连接。函数原型: int shutdown(SOCKET s,int how );返回值:请参考bind()函数。
- shutdown()函数参数说明
s标识一个套接口的描述字
how是一个标志,用于描述禁止哪些操作,取值如下表所示
- 关闭套接口-closesocket()
shutdown函数只关闭读写通道,并不关闭套接口,且套接口所占有的资源将被一直保留到closesocket()调用之前。一个套接口不再使用时一定要关闭这个套接口,以释放与该套接口关联的所有资源,包括等候处理的数据。函数原型: int closesocket(SOCKET s);函数参数:s表示即将被关闭的套接口返回值:请参考bind()函数
9.无连接的C/S程序工作流程图
- recvfrom()
对于无连接的套接口来说,要从套接口上接收一个数据报并保存发送数据的源地址,就要使用recvfrom()函数。函数原型:int recvfrom( SOCKET s,char FAR * buf, int len,int flags, struct sockaddr FAR * from, int FAR * fromlen);函数参数:s标识一个套接口的描述字buf接收数据的缓冲区len接收数据缓冲区的长度flags调用操作方式,同recv()中的flagsfrom可选指针,指向装有源地址的缓冲区fromlen可选指针,指向from缓冲区的长度值函数说明:该函数的用法与有连接时recv()的用法一致,要注意的是该函数也可以用于有连接时数据的接收
- sendto()
对于无连接的套接口来说,要从套接口上发送一个数据报,就要使用sendto()函数函数原型:int sendto(SOCKET s,const char FAR * buf, int len,int flags, const struct sockaddr FAR * to,int tolen);函数参数:s本机的套接字buf待发送数据的缓冲区len指明buf缓冲区中要发送的数据长度flags调用方式标志位,同send()中的flagsto可选指针,指向接收数据的目的套接口地址tolen是to所指的地址的长度函数说明:该函数的使用方法类似send()函数,当用于无连接套接字接口,调用函数前要设置,指出目标IP地址和目标端口号。如果用于有连接的套接口时,则不能指定目标地址和目标端口,将to设置为空,地址长度设为0。当然在有连接的情况下很少使用该函数
-
WSAStartup的使用按下F1查看帮助
-
bind的F1帮助以及索引
查下他的返回值,看看都是整数
-
错误码查看
若调试出现
-
eg:服务端使用do_server()
P99\TcpSrv\main.cpp,P99\TcpCli\main.cpp
P99\TcpSrv\main.cpp
#pragma comment(lib, "ws2_32.lib")#include <WinSock2.h>#include <process.h>#include "JThread.h"#include <iostream>using namespace std;// class ServiceThread : public JThread// { // public:// ServiceThread(SOCKET conn) : conn_(conn)// { // cout<<"ServiceThread ..."<<endl;// }// ~ServiceThread()// { // cout<<"~ServiceThread ..."<<endl;// }// void Run()// { // char buf[1024] = {0};// while (1)// { // int ret = recv(conn_, buf, sizeof(buf), 0);// if (ret == SOCKET_ERROR)// { // cout<<"error with code = "<<WSAGetLastError()<<endl;// exit(1);// }// if (ret == 0)// { // cout<<"client close"<<endl;// break;// }// if (ret > 0)// { // cout<<buf<<endl;// send(conn_, buf, strlen(buf), 0);// }// memset(buf, 0, sizeof buf);// }// closesocket(conn_);// }// private:// SOCKET conn_;// };//void do_service(SOCKET conn){ char buf[1024] = { 0}; while (1) { int ret = recv(conn, buf, sizeof(buf), 0); if (ret == SOCKET_ERROR) { cout<<"error with code = "<<WSAGetLastError()<<endl; exit(1); } if (ret == 0)//说明对等方关闭了 { cout<<"client close"<<endl; break; } if (ret > 0) { cout<<buf<<endl; send(conn, buf, strlen(buf), 0); } memset(buf, 0, sizeof buf); } closesocket(conn);}int main(void){ WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 );//表示要请求的版本 //来自F1的eg start err = WSAStartup( wVersionRequested, &wsaData );//启动它 if ( err != 0 ) { return 1; } //判断请求的是不是2.2版本 if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup( ); return 1; } SOCKET listenfd; listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (listenfd == INVALID_SOCKET) { cout<<"error with code = "<<WSAGetLastError()<<endl; exit(1); } //来自F1的eg end //准备一个服务器地址 sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; //servaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//Windows写法 //Linux写法如下 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//S_un.S_addr等于s_addr,用宏做的 servaddr.sin_port = htons(8888); int ret; int opt = 1; ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)); if (ret == SOCKET_ERROR) { cout<<"error with code = "<<WSAGetLastError()<<endl; exit(1); } //绑定他 //C语言不能省略struct,C++可以 //C语言写法:ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); ret = bind(listenfd, (sockaddr*)&servaddr, sizeof(servaddr)); //SOCKET_ERROR定义是-1 if (ret == SOCKET_ERROR) { cout<<"error with code = "<<WSAGetLastError()<<endl; exit(1); } ret = listen(listenfd, SOMAXCONN);//SOMAXCONN监听队列最大值 if (ret == SOCKET_ERROR) { cout<<"error with code = "<<WSAGetLastError()<<endl; exit(1); } //接受客户端的连接 SOCKET conn; sockaddr_in peeraddr;//对等方的地址 int peerlen;//linux底下是socklen_t while (1) { peerlen = sizeof(peeraddr);//peerlen是输入输出参数 conn = accept(listenfd, (sockaddr*)&peeraddr, &peerlen); if (conn == INVALID_SOCKET) { cout<<"error with code = "<<WSAGetLastError()<<endl; exit(1); } //成功打印对等方的地址 cout<<inet_ntoa(peeraddr.sin_addr)<<" "<<ntohs(peeraddr.sin_port)<<endl; do_service(conn); //unsigned threadId; //HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, do_service, (void*)conn, 0, &threadId); //if (hThread == NULL) //{ // cout<<"error with code = "<<GetLastError()<<endl; // exit(1); //} //CloseHandle(hThread); // ServiceThread* t = new ServiceThread(conn); // t->SetAutoDel(true); // t->Start(); } WSACleanup(); return 0;}
P99\TcpCli\main.cpp
#pragma comment(lib, "ws2_32.lib")#include <WinSock2.h>#include <iostream>using namespace std;int main(void){ //来自F1的eg start WORD wVersionRequested;//表示要请求的版本 WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup( wVersionRequested, &wsaData );//启动它 if ( err != 0 ) { return 1; } //判断请求的是不是2.2版本 if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup( ); return 1; } //来自F1的eg end //创建socket SOCKET sock; //协议族:TCP/IP //socket类型:流失套接字 //指定TCP协议,或者写0,让内核自动选择 sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //~0表示,0取反就是全1,INVALID_SOCKET就是~0 if (sock == INVALID_SOCKET) { //WSAGetLastError()类似于Linux的errno cout<<"1error with code = "<<WSAGetLastError()<<endl; exit(1); } //准备一个服务器地址 sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; //servaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//Windows写法 //Linux写法如下 servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//S_un.S_addr等于s_addr,用宏做的 servaddr.sin_port = htons(8888); int ret; ret = connect(sock, (sockaddr*)&servaddr, sizeof(servaddr)); if (ret == -1) { cout<<"2error with code = "<<WSAGetLastError()<<endl; exit(1); } char buf[1024] = { 0}; char recvbuf[1024] = { 0}; while (1) { cin>>buf; if (strcmp(buf, "quit") == 0) break; ret = send(sock, buf, strlen(buf), 0); if (ret == -1) { cout<<"3error with code = "<<WSAGetLastError()<<endl; exit(1); } ret = recv(sock, recvbuf, sizeof(buf), 0); if (ret == -1) { cout<<"4error with code = "<<WSAGetLastError()<<endl; exit(1); } if (ret == 0)//说明服务器关闭 { cout<<"server close"<<endl; break; } if (ret > 0) { cout<<recvbuf<<endl; } memset(buf, 0, sizeof buf);//C++的sizeof没有括号也行 memset(recvbuf, 0, sizeof recvbuf); } closesocket(sock); return 0;}
- 测试
发表评论
最新留言
感谢大佬
[***.8.128.20]2025年04月06日 21时17分39秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
[网站公告]11月26日00:00-04:00阿里云RDS升级
2021-05-09
[网站公告]又拍云API故障造成图片无法上传(已恢复)
2021-05-09
云计算之路-阿里云上:“黑色30秒”走了,“黑色1秒”来了,真相也许大白了
2021-05-09
上周热点回顾(6.9-6.15)
2021-05-09
上周热点回顾(10.20-10.26)
2021-05-09
上周热点回顾(2.16-2.22)
2021-05-09
上周热点回顾(3.2-3.8)
2021-05-09
.NET跨平台之旅:借助ASP.NET 5 Beta5的新特性显示CLR与操作系统信息
2021-05-09
上周热点回顾(7.27-8.2)
2021-05-09
上周热点回顾(5.9-5.15)
2021-05-09
上周热点回顾(1.16-1.22)
2021-05-09
上周热点回顾(1.23-1.29)
2021-05-09
上周热点回顾(3.20-3.26)
2021-05-09
上周热点回顾(6.19-6.25)
2021-05-09
云计算之路-阿里云上:docker swarm 集群故障与异常
2021-05-09
上周热点回顾(2.19-2.25)
2021-05-09
云计算之路-阿里云上:博客web服务器轮番CPU 100%
2021-05-09
云计算之路-阿里云上:服务器CPU 100%问题是memcached连接数限制引起的
2021-05-09
上周热点回顾(3.26-4.1)
2021-05-09