套接字编程(VC_Win32)
目录
(本章节中例子都是用 VS2005 编译调试的)
相关文献:
套接字概述
简介(源于维基)
Berkeley套接字(也作BSD套接字应用程序接口)刚开始是4.2BSD Unix操作系统(于1983发布)的一套应用程序接口。然而,由于AT&T的专利保护着UNIX,所以只有在1989年Berkeley大学才能自由地发布自己的操作系统和网络库。
Berkeley套接字接口,一个应用程序接口(API),使用一个Internet套接字的概念,使主机间或者一台计算机上的进程间可以通讯。 它可以在很多不同的输入/输出设备和驱动之上运行,尽管这有赖于操作系统的具体实现。 接口实现用于TCP/IP协议,因此它是维持Internet的基本技术之一。 它是由加利福尼亚的伯克利大学开发,最初用于Unix系统。 如今,所有的现代操作系统都有一些源于Berkeley套接字接口的实现,它已成为连接Internet的标准接口。
套接字接口的接入有三个不同的级别,最基础的也是最有效的就是raw socket级别接入。 很少的应用程序需要在外向通讯控制的这个级别接入,所以raw socket级别是只为了用于开发计算机Internet相关技术的。 最近几年,大多数的操作系统已经实现了对它的全方位支持,包括Windows XP。
应用程序网络数据传输
- 端口:
- 定义: 一种抽象的软件结构,应用程序通过系统调用与某端口建立连接后,传输层给该端口的数据都被相应的进程接收,相应的进程发给传输层的数据都通过该端口输出
- 端口号: 一个整形标示符,来表示端口,取值为0~65535,1024以下的端口保留给预定义的服务
- 注意: TCP/IP传输层的两个协议TCP与UDP是完全独立的两个软件模块,因此各自端口独立,也就是说TCP/UDP可以拥有相同的端口号
- IP 地址:
- 所谓IP地址就是给每个连接在Internet上的主机分配的一个32bit地址。按照TCP/IP协议规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节
套接字
- 说明
- 套接字存在于通信域中,通信域也叫地址簇,它是一个抽象的概念,主要用于将通过套接字通信的进程的共有特性,综合在一起,套接字通常只与同一区域的套接字交换数据,(也可能跨区通信,但这只是在执行某种转换进程后才能实现).Windows Sockets只支持一个通信域,网际域(AF_INET),这个域被使用网际协议簇通信进程使用
- 套接字类型
- SOCK_STREAM 流式套接字,提供面向连接的数据传输服务,数据无差错,无重复的发送,并且按发送顺序接送,流式套接字实际上是基于TCP实现的
- SOCK_DGRAM 数据报式套接字,提供无连接服务,数据包以独立包形式发送,不提供无错保证,数据可能存在丢失或重复,并且接受顺序混乱,数据报式套接字实际上是基于UDP协议实现的
- SOCK_RAM 原始套接字
网络字节顺序(套接字与地址簇中使用)
由于不同的计算机存放数据字节的顺序不同,所以通信双方必须协商出统一的存放字节顺序,这样才能发送方的数据可以被接收方准确无误的读取,否则接收方读到的是一堆不知名的数据,所以通信前双方必须协商统一的用网络字节顺序,保证通信的正常进行
基于消息的异步套接字
- 两种模式 (Windows套接字在两种模式下执行I/O操作)
- 阻塞 在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回程序(将控制权交还给程序)
- 非阻塞 在非阻塞模式下,Winsock函数无论如何都会立即返回
- 消息驱动
- Windows Sockets为了支持Windows消息驱动机制,使应用程序开发者能够方便地处理网络通信,它对网络事件采用了基于消息的异步存取策略
- Windows Sockets的异步选择函数WSAAsyncSelect()提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息
C/S模式
相关函数
[win32 API 相关套接字函数][相关结构体及宏][基于消息的套接字编程][MFC套接字相关函数]
Win32 API 相关套接字常用函数
[套接字版本协商][创建套接字][绑定端口][点分十进制转换成无符号长整形][无符号长整形转换成点分十进制][主机字节顺序转换为网络字节顺序][TCP套接字相关函数][UDP套机制相关函数]
函数原型
int WSAStartup ( WORD wVersionRequested, LPWSADATA lpWSAData );
参数说明
- wVersionRequested: 用来指定准备加载的Winsock库的版本.高字节位指定所需要的Winsock库的副版本,而低字节则是主版本.通常版本为2.1其中2为主版本,1为副版本号
- lpWSAdata: 这是一个返回值,指向WSADATA结构的指针,WSAStartup函数用其来加载的库版本有关的信息填在这个结构中
返回值
- 如果Ws_32.dll或底层网络子系统没有正确初始化或被找到,函数返回WSASYSNOTREADY.
- 如果请求的版本低于Winsock动态库所支持的最低版本,WSAStartup函数将返回WSAVERNOTSUPPORTED.
- 如果请求的版本等于或者高于Winsock动态库所支持的最低版本WSAData的wVersion成员包含你的应用程序应该使用的版本,它是动态库所支持的最高版本与请求版本中较小的那个
函数原型
SOCKET socket ( int af, int type, int protocol );
参数说明
- af: 指定地址簇,对于TCP/IP协议的套接字,它只能写成AF_INET(或PF_INET)
- type: 指定套接字类型,它只支持两种套接字,SOCK_STREAM(流式套接字),SOCK_DGRAM(数据报式套接字)
- protocol: 指定与特定地址家族相关的协议,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议
返回值
- 如果成功,返回一个新的SOCKET数据类型的套接字描述符
- 如果失败,返回一个INVALID_SOCKET,错误信息可以通过WSAGetLastError函数返回
函数原型
int bind ( SOCKET s, const struct sockaddr FAR* name, int namelen );
参数说明
- s: 指定要绑定的的套接字
- name: 指定该套接字的本地地址信息,指向SOCKADDR结构体的指针
- namelen: 指定该地址结构的长度
返回值 如果成功,返回0,如果失败,返回SOCKET_ERROR,错误信息可以通过WSAGetLastError函数返回
函数原型
unsigned long inet_addr (const char FAR * cp );
参数说明: cp 一个点分十进制的IP地址形式的字符串
返回值 一个对应cp点分十进制的unsigned long类型的数值
函数原型
char FAR * inet_ntoa (struct in_addr in );
参数说明 in: 一个点分十进制的unsigned long类型的数值
返回值 一个对应in点分十进制的IP地址形式的字符串
16位数值
- 函数原型
u_short htons (u_short hostshort );
- 参数说明: hostshort 一个以主机字节顺序表示的16位数值
- 返回值: 把一个u_short类型的值从主机字节顺序转换为网络字节顺序
32位数值
- 函数原型
u_long htonl (u_long hostlong );
- 参数说明: hostlong: 一个以主机字节顺序表示的32位数值
- 返回值: 把一个u_long类型的值从主机字节顺序转换为网络字节顺序
相关结构体及宏
[套接字版本结构][地址结构][TCP/IP地址结构][地址表示][MAKEWORD宏]
struct WSAData { WORD wVersion;//打算使用的Winsock版本号 WORD wHighVersion;//容纳的是现有的Winsock最高版本号,以高字节代表的是Winsock的副版本,低字节表示的是搞版本号 char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYSSTATUS_LEN+1]; //以下两个参数一般不设置它 unsigned short iMaxSockets;//同时最多可以打开多少套接字 unsigned short iMaxUdpDg;//数据报的最大长度 //同时最多可以打开套接字数目很大程度上和可用物理内存的多少有关 char FAR * lpVendorInfo; //这个参数Winsock实施方案有关的指定厂商信息预留的,任何一个Win32平台上都没有使用这个字段 };
struct sockaddr { unsigned short sa_family;//指定地址家族,对于TCP/IP协议的套接字,必须设置为AF_INET char sa_data[14];// 仅仅表示要求一块内存分配区,启到占位作用,该区域中指定与协议相关的具体地址信息,由于实际要求的只是内存区,所以对于不同的协议家族,用不同的协议家族,用不同的结构来替换sockaddr };
struct sockaddr_in{ short sin_family;///指定地址家族,对于TCP/IP协议的套接字,必须设置为AF_INET unsigned short sin_port;//指定要分配给套接字的端口 struct in_addr sin_addr;//套接字的主机的IP地址 char sin_zero[8];//一个填充占位符 };//在TCP/IP编程中用这个结构体来替换sockaddr结构体
struct in_addr { union { struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { u_short s_w1,s_w2; } S_un_w; u_long S_addr; } S_un; }; //用于记入地址的一个结构,若将IP地址设置为INADDR_ANY,允许套接字向任何分配给本机的IP地址发送或接收数据,用INADDR_ANY可以简化编程,这样程序便可以接收发自多个接口的回应
MAKEWORD(x,y)
作用: 用于设置DWORD类型的版本号,x是高字节,y是低字节
基于消息套接字编程相关相关函数
[获得系统中安装的网络协议的相关信息][注册网络事件][创建套接字][UDP消息接收][UDP消息发送]
函数原型
int WSAEnumProtocols( LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, ILPDWORD lpdwBufferLength )
参数说明
- lpiProtocols: 一个以NULL结尾的协议标识号数组。这个参数是可选的,如果lpiProtocols为NULL,则返回所有可用协议的信息,否则,只返回数组中列出的协议信息,
- lpProtocolBuffer[out]: 一个用WSAPROTOCOL_INFO结构体填充的缓冲区。 WSAPROTOCOL_INFO结构体用来存放或得到一个指定协议的完整信息
- lpdwBufferLength[in, out]: 在输入时,指定传递给WSAEnumProtocols()函数的lpProtocolBuffer缓冲区的长度;在输出时,存有获取所有请求信息需传递给WSAEnumProtocols ()函数的最小缓冲区长度。这个函数不能重复调用,传入的缓冲区必须足够大以便能存放所有的元素。这个规定降低了该函数的复杂度,并且由于一个 机器上装载的协议数目往往是很少的,所以并不会产生问题
返回值 如果函数没有错误发生,函数返回协议报告信息,如果错误返回SOCKET_ERROR和在WSAGetLastError函数中查询相关详细信息
说明 Win32平台支持多种不同的网络协议,采用Winsock2,就可以编写可直接使用任何一种协议的网络应用程序了。通过WSAEnumProtocols函数可以获得系统中安装的网络协议的相关信息
函数原型
int WSAAsyncSelect ( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );
参数说明
- s: 标识请求网络事件通知的套接字
- hWnd: 指定网络事件发生时接收消息的窗口句柄
- wMsg: 指定网络事件发生时窗口接收的消息
- lEvent: 指定应用程序感兴趣的网络事件,可以是下面的组合
取值 说明
- FD_READ 应用程序想接收有关是否可读的通知
- FD_WRITE 应用程序想接收有关是否可读的通知
- FD_OBB 应用程序想要接收是否外带(OBB)数据抵达的通知
- FD_ACCEPT 应用程序想要接收与进入连接有关的通知
- FD_CONNECT 应用程序想要接收连接操作以完成的通知
- FD_CLOSE 应用程序想要接收与套接字关闭有关的通知
- FD_QOS 应用程序想要接收套接字"服务质量"发生更改的通知
- FD_GROUP_QOS 应用程序想要接收套接字组""服务质量"发生更改的通知
- FD_ROUTING_INTERFACE_CHANGE 应用程序想要接收在指定的方向上,与路由接口发生变化有关的通知
- FD_ADDRESS_LIST_CHANGE 应用程序想要接收,针对套接字的协议家族,本地地址列表发生变化的通知
返回值 如果函数成功返回值是0,如果函数失败则返回值是SOCKET_ERROR并调用WSAGetLastError获得更多错误信息
说明 该函数为指定的套接字请求基于Windows消息的网络事件通知,并自动将该套接字设置为非阻塞模式
函数原型
SOCKET WSASocket( int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags );
参数说明
- af: 指定地址簇,对于TCP/IP协议的套接字,它只能写成AF_INET(或PF_INET)
- type: 指定套接字类型,它只支持两种套接字,SOCK_STREAM(流式套接字),SOCK_DGRAM(数据报式套接字)
- protocol: 指定与特定地址家族相关的协议,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议
- lpProtocolInfo: 一个指向WSAPROTOCOL_INFO结构体的指针,该结构定义了所创建的套接字的特性。如果lpProtocolInfo为NULL,则WinSock2 DLL使用前三个参数来决定使用哪一个服务提供者,它选择能够支持规定的地址族、套接字类型和协议值的第一个传输提供者。如果lpProtocolInfo不为NULL,则套接字绑定到与指定的结构WSAPROTOCOL_INFO相关的提供者
- g: 保留
- dwFlags: 套接字属性的描述
返回值
- 如果成功,返回一个新的SOCKET数据类型的套接字描述符
- 如果失败,返回一个INVALID_SOCKET,错误信息可以通过WSAGetLastError函数返回
MFC常用函数
[初始化套接字]
函数原型
BOOL AfxSocketInit(WSADATA* lpwsaData = NULL);
作用: MFC提供的创建套接字库的函数
返回值: 若函数调用成功时候返回非零值,否则返回零
注意: 应该在应用程序类重载的InitInstance函数中调用AfxSocketInit函数在MFC应用程序运行时需要的一些必要的预编译头文件stdafx.h中添加函数的头文件afxsock.h
优点: 使用这个函数的优点,它可以确保在应用程序终止前,调用WSACleapup函数以终止对套接字的使用,并且利用AfxSocketInit函数也不用在加载套接字库时,手动为工程添加到ws2_32.lib的链接库文件设置
TCP套接字相关函数
[监听请求(服务器)][接收请求(服务器)][发送数据][接收数据][建立连接(客服端)]
函数原型
int listen ( SOCKET s, int backlog );
参数说明
- s: 对应于用于监听用的套接字
- backlog: 是等待队列的最大长度,如果设置为SOMAXCONN,那么下层的服务提供者将负责将这个套接字设置为最大的合理值(设置这个值是为了设置等待连接队列的最大长度,而不是在一个端口上同时可以进行连接的数目,例如将backlog设置为2,当有3个请求同时到达时候,前两个连接请求会被放到请求连接队列中,然后由应用程序依次为这些请求服务,而第三个请求就被拒绝了)
函数原型
SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen );
参数说明
- s: 对应于用于监听的套接字
- addr: 指向一个缓冲区的指针,该缓冲区用来接收连接实体的地址,也就是当客户端向服务器发起的连接,服务器接收这个连接时,保存发起连接的这个客户端的IP地址信息和端口信息
- addrlen: 是一个返回值,指向一个整形,返回包含地址信息的长度
返回值
如果没有错误发生,函数返回一个建立好连接的SOCKET套接字,如果失败则返回INVALID_SOCKET
函数原型
int send ( SOCKET s, const char FAR * buf, int len, int flags );
参数说明
- s: 是指向一个已建立连接的套接字
- buf: 指向一个缓冲区的指针,该缓冲包含将要传递的数据
- len: 是缓冲区的长度
- flags: 设定的值将影响函数行为,一般设置为0,MSG_PEEK会使有用的数据被复制到接收缓冲区内,但没有从系统缓冲区中将其删除MSG_OOB表示处理带外数据
函数原型
int recv ( SOCKET s, char FAR* buf, int len, int flags );
参数说明
- s: 是指向一个已建立连接的套接字
- buf: 指向一个缓冲区的指针,该缓冲用来接收保存数据
- len: 是缓冲区的长度
- flags: 设定的值将影响函数行为,一般设置为0,MSG_PEEK会使有用的数据被复制到接收缓冲区内,但没有从系统缓冲区中将其删除MSG_OOB表示处理带外数据
函数原型
int connect ( SOCKET s, const struct sockaddr FAR* name, int namelen );
参数说明
- s: 用来建立连接的套接字
- name: 设定连接的服务器端的地址信息
- namelen: 指定服务器端地址信息的长度
UDP套接字相关函数
[接收消息][发送数据][接收消息(基于消息)][发送数据(基于消息)]
函数原型
int recvfrom ( SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen );
参数说明
- s: 准备接收数据的套接字
- buf: 指向一个缓冲区的指针,该缓冲包含将要传递的数据
- len: 是缓冲区的长度
- flags: 设定的值将影响函数行为,一般设置为0
- from: 一个指向地址结构类型的指针,主要是用来接收发送数据方的地址信息
- fromlen: 它是一个in/out类型参数,表面在调用需要给它一个初始值,当函数调用成功后,会通知这个参数返回一个值,fai返回值是地址结构的大小
返回值: 如果没有错误发送返回值是接收数据的字节数,如果连接关闭则返回0,否则返回SOCKET_ERROR
函数原型
int sendto ( SOCKET s, const char FAR * buf, int len, int flags, const struct sockaddr FAR * to, int tolen );
参数说明
- s: 准备发送数据的套接字
- buf: 指向一个缓冲区的指针,该缓冲用来接收保存数据
- len: 是缓冲区的长度
- flags: 设定的值将影响函数行为,一般设置为0
- to: 一个指向地址结构类型的指针,主要是用来要指定目标套截止的地址
- tolen: 指定目标套接字的地址的长度
返回值: 如果没有错误返回值是发送数据的字节数,否则返回SOCKET_ERROR
函数原型
int WSARecvFrom( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, struct sockaddr FAR *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
参数说明
- s: 标识套接字的描述符
- lpBuffers[in, out]: 一个指向WSABUF结构体的指针。每一个WSABUF结构体包含一个缓冲区的指针和缓冲区的长度
typedef struct __WSABUF { u_longlen; // buffer length char FAR *buf; // pointer to buffer } WSABUF, FAR * LPWSABUF;
- dwBufferCount: lpBuffers数组中WSABUF结构体的数目
- lpNumberOfBytesRecvd[out]: 如果接收操作立即完成,则为一个指向本次调用所接收的字节数的指针
- lpFlags[in, out] 一个指向标志位的指针,可以对以下进行组合
值 说明- MSG_PEEK 浏览到来的数据,这些数据将复制到缓冲区,但并不从输入队列中移除,此标记仅对非重叠套接字有效
- MSG_OBB 处理外带(OBB)数据
- MSG_PARTIAL 此标记仅用于面向消息的套接字,作为输出参数时,此标记表面数据是发送方传送的一部分,消息的剩余部分将在随后的接收操作中被传送,如果随后的某个接收操作没有此标志,就表明这时发送方发送消息的尾部,作为输入参数时,此标记,表面接收操作是完成的,即使只是一条消息部分数据已被服务提供者所接收
- lpFrom[out]: 可选指针,指向重叠操作完成后存放源地址的缓冲区
- lpFromlen[in, out]: 指向from缓冲区大小的指针,仅当指定了lpFrom才需要
- lpOverlapped: 一个指向WSAOVERLAPPED结构体的指针(对于非重叠套接字则忽略)
- lpCompletionRoutine: 一个指向接收操作完成时调用的完成例程的指针(对于非重叠套接字则忽略)
如果创建的是重叠套接字,在使用函数时,一定要注意后面两个参数值,因为这时采用重叠IO操作,函数会立即返回,但接收到数据这一操作完成以后操作系统会调用lpCompletionRoutine参数指定的例程类通知调用进程,函数原型 -
void CALLBACK CompletionROUTINE( IN DWORDdwError, IN DWORDcbTransferred, IN LPWSAOVERLAPPEDlpOverlapped, IN DWORDdwFlags );
返回值: 若函数失败则返回SOCKET_ERROR
函数原型
int WSASendTo( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr FAR *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
参数说明
- s: 标识一个套接字(可能已连接)的描述符
- lpBuffers: 一个指向WSABUF结构体的指针。每一个WSABUF结构体包含一个缓冲区的指针和缓冲区的长度
- dwBufferCount: lpBuffers数组中WSABUF结构体的数目
- lpNumberOfBytesSent[out]: 如果发送操作立即完成,则为一个指向本次调用所发送的字节数的指针
- dwFlags: 指示影响操作行为的标志位
Value Meaning- MSG_DONTROUTE Specifies that the data should not be subject to routing. A Windows Socket service provider may choose to ignore this flag.
- MSG_OOB Send out-of-band data (stream-style socket such as SOCK_STREAM only).
- MSG_PARTIAL Specifies that lpBuffers only contains a partial message. Note that the error code WSAEOPNOTSUPP will be returned by transports that do not support partial message transmissions.
- lpTo: 可选指针,指向目标套接字的地址
- iToLen: lpTo中地址的长度
- lpOverlapped: 一个指向WSAOVERLAPPED结构的指针(对于非重叠套接字则忽略)
- lpCompletionRoutine: 一个指向接收操作完成时调用的完成例程的指针(对于非重叠套接字则忽略)
返回值: 若函数失败则返回SOCKET_ERROR
编写套接字通信
[编写基于 UDP 套接字通信][编写基于 TCP 套接字通信][编写基于消息机制的 UDP 套接字通信][通过域名获得 IP 地址]
编写基于 UDP 套接字通信
流程图:
代码示例:
查看本机 IP
服务器端
#include <Winsock2.h> #include <iostream> #include <string> #pragma comment(lib,"ws2_32.lib") using namespace std; void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData );//该函数的功能是加载一个Winsocket库版本 if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //创建套接字 SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //将套接字绑定到端口上 bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); char recvBuffer[300];//接收字符数据 memset((void*)recvBuffer,'\0',300); cout<<"等待对方发送数据... "<<endl; //接收数据 recvfrom(sockSrv,recvBuffer,300,0,(SOCKADDR*)&addrClient,&len); cout<<"对方的地址为: "<<inet_ntoa(addrClient.sin_addr)<<endl; cout<<"接收的内容为: "<<recvBuffer<<endl; //发送数据 string sendBuffer = "this is server"; cout<<"向客户端方发送数据: "<<sendBuffer.c_str()<<endl; sendto(sockSrv,sendBuffer.c_str(),sendBuffer.length() +1,0,(SOCKADDR*)&addrClient,sizeof(SOCKADDR)); closesocket(sockSrv);//关闭服务器套接字 WSACleanup();//结束套接字库的调用 system("pause"); }
客户端
#include <Winsock2.h> #include <iostream> #include <string> //加载动态连接库ws2_32.dll,提供了网络相关API的支持 #pragma comment(lib,"ws2_32.lib") using namespace std; void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData );//该函数的功能是加载一个Winsocket库版本 if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //建立通讯 socket SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr=inet_addr("220.160.249.188"); addrSrv.sin_family=AF_INET; addrSrv.sin_port=htons(6000); //发送数据 string sendBuffer = "this is client!"; cout<<"向服务器方发送数据: "<<sendBuffer.c_str()<<endl; sendto(sockClient,sendBuffer.c_str(),sendBuffer.length()+1,0,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //接收数据 char recvBuffer[300];//接收字符数据 memset((void*)recvBuffer,'\0',300); int len = sizeof(SOCKADDR); cout<<"等待对方发送数据... "<<endl; recvfrom(sockClient,recvBuffer,300,0,(SOCKADDR*)&addrSrv,&len); cout<<"主机的地址为: "<<inet_ntoa(addrSrv.sin_addr)<<endl; cout<<"接收的内容为: "<<recvBuffer<<endl; //结束通信 closesocket(sockClient);//关闭服务器套接字 WSACleanup();//结束套接字库的调用 system("pause"); }
运行结果(先运行服务器,再运行客户端.然后结果为下图所示上面为服务器下面为客户端)
编写基于 TCP 套接字通信
流程图:
代码示例:
查看本机 IP
服务器端
#include <Winsock2.h> #include <iostream> #include <string> #pragma comment(lib,"ws2_32.lib") using namespace std; void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData );//该函数的功能是加载一个Winsocket库版本 if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //创建套接字 SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(6000); //将套接字绑定到端口上 bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //将套接字设置为监听模式 listen(sockSrv,5); //等待客户请求来到,当请求来到时候,接受请求,接受连接请求,返回一个新的对应于此连接套接字 SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); //开始监听 cout<<"等待用户连接"<<endl; SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrClient,&len);//sockConn用于建立连接的套接字 cout<<"用户连接到来"<<endl; //和客户通信 //接收数据 char recvBuffer[300];//接收字符数据 memset((void*)recvBuffer,'\0',300); cout<<"等待对方发送数据... "<<endl; recv(sockConn,recvBuffer,100,0); cout<<"对方的地址为: "<<inet_ntoa(addrClient.sin_addr)<<endl; cout<<"接收的内容为: "<<recvBuffer<<endl; //发送数据 string sendBuffer = "this is server"; cout<<"向客户端方发送数据: "<<sendBuffer.c_str()<<endl; send(sockConn,sendBuffer.c_str(),sendBuffer.size(),0); //关闭本次连接的通道 closesocket(sockConn); closesocket(sockSrv);//关闭服务器套接字 WSACleanup();//结束套接字库的调用 system("pause"); }
客户端
#include <Winsock2.h> #include <iostream> #include <string> //加载动态连接库ws2_32.dll,提供了网络相关API的支持 #pragma comment(lib,"ws2_32.lib") using namespace std; void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData );//该函数的功能是加载一个Winsocket库版本 if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //建立通讯 socket SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr=inet_addr("220.160.249.188"); addrSrv.sin_family=AF_INET; addrSrv.sin_port=htons(6000); //发出连接请求 cout<<"请求与服务器连接"<<endl; if(connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)) != SOCKET_ERROR) { cout<<"与服务器建立连接"<<endl; //和服务器通信 //发送数据 string sendBuffer = "this is client!"; cout<<"向服务器方发送数据: "<<sendBuffer.c_str()<<endl; send(sockClient,sendBuffer.c_str(),sendBuffer.size(),0); //接收数据 char recvBuffer[300];//接收字符数据 memset((void*)recvBuffer,'\0',300); int len = sizeof(SOCKADDR); cout<<"等待对方发送数据... "<<endl; recv(sockClient,recvBuffer,100,0); cout<<"主机的地址为: "<<inet_ntoa(addrSrv.sin_addr)<<endl; cout<<"接收的内容为: "<<recvBuffer<<endl; } //结束通信 closesocket(sockClient);//关闭服务器套接字 WSACleanup();//结束套接字库的调用 system("pause"); }
运行结果(先运行服务器,再运行客户端.然后结果为下图所示上面为服务器下面为客户端)
编写基于消息机制的 UDP 套接字通信
流程图:
程序代码(源于孙鑫第十六讲代码):
先添加一个对话框工程工程工程名为 Chat ,因为服务器和客户端这里写在一起所以就一个工程就好.
资源界面设计
相关代码
在 stdafx.h 头文件中加载动态连接库的引入库和相关头文件
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
在程序初始化的时候, 加载套接字库和进行套接字库协商,这个工作放在 Chat.cpp ,主线程的初始化工作函数 InitInstance 中.
BOOL CChatApp::InitInstance() { //套接字版本协商------------------------------------ WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return FALSE; } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup( ); return FALSE; } //---------------------------------------------------- AfxEnableControlContainer(); // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need. #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif CChatDlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Place code here to handle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here to handle when the dialog is // dismissed with Cancel } // Since the dialog has been closed, return FALSE so that we exit the // application, rather than start the application's message pump. return FALSE; }
接着来定义一个网络事件,和网络事件响应函数
/* 在 ChatDlg.h 中 ******************************************************/ //添加网络事件定义 #define UM_SOCK WM_USER+1 //然后在 CChatDlg 类中添加消息响应函数原型 afx_msg LRESULT OnSock(WPARAM,LPARAM); /* 在 ChatDlg.cppp 中 ***************************************************/ //在消息映射中添加消息映射 BEGIN_MESSAGE_MAP(CChatDlg, CDialog) //{{AFX_MSG_MAP(CChatDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BTN_SEND, OnBtnSend) //}}AFX_MSG_MAP ON_MESSAGE(UM_SOCK,OnSock) //网络事件的消息映射 END_MESSAGE_MAP() //添加消息事件响应函数定义 afx_msg LRESULT CChatDlg::OnSock(WPARAM wParam,LPARAM lParam) { switch(LOWORD(lParam)) { //接收数据 case FD_READ: WSABUF wsabuf; wsabuf.buf=new char[200]; wsabuf.len=200; DWORD dwRead; DWORD dwFlag=0; SOCKADDR_IN addrFrom; int len=sizeof(SOCKADDR); CString str; CString strTemp; HOSTENT *pHost; if(SOCKET_ERROR==WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag, (SOCKADDR*)&addrFrom,&len,NULL,NULL)) { MessageBox("接收数据失败!"); return 0; } pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET); //str.Format("%s说 :%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf); str.Format("%s说 :%s",pHost->h_name,wsabuf.buf); str+="\r\n"; GetDlgItemText(IDC_EDIT_RECV,strTemp); str+=strTemp; SetDlgItemText(IDC_EDIT_RECV,str); break; } return 0; }
定义好网络世界和网络世界响应函数后我们来进行套接字的创建和和端口绑定,以及网络事件注册,这个功能我们封装在 InitSocket 并且在对话框初始化时候我们就打算调用它.
/* 在 ChatDlg.h 中 ******************************************************/ //首先我们为 CChatDlg 类添加一个套接字成员变量,用于网络通信 SOCKET m_socket; //然后在 CChatDlg 类中添加函数原型 BOOL InitSocket(); /* 在 ChatDlg.cppp 中 ***************************************************/ //定义 InitSocket 函数 BOOL CChatDlg::InitSocket() { //套接字创建 m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0); if(INVALID_SOCKET==m_socket) { MessageBox("创建套接字失败!"); return FALSE; } //服务器端的端口绑定 SOCKADDR_IN addrSock; addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY); addrSock.sin_family=AF_INET; addrSock.sin_port=htons(6000); if(SOCKET_ERROR==bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR))) { MessageBox("绑定失败!"); return FALSE; } //注册网络事件 if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ)) { MessageBox("注册网络读取事件失败!"); return FALSE; } return TRUE; }
在定义发送按钮消息事件让其能发送对应消息
void CChatDlg::OnBtnSend() { // TODO: Add your control notification handler code here DWORD dwIP; CString strSend; WSABUF wsabuf; DWORD dwSend; int len; CString strHostName; SOCKADDR_IN addrTo; HOSTENT* pHost; //客户端 //获取服务器端 IP 地址 ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP); addrTo.sin_addr.S_un.S_addr=htonl(dwIP); //绑定端口和设置IP协议 addrTo.sin_family=AF_INET; addrTo.sin_port=htons(6000); //获得发送文本 GetDlgItemText(IDC_EDIT_SEND,strSend); len=strSend.GetLength(); wsabuf.buf=strSend.GetBuffer(len); wsabuf.len=len+1; SetDlgItemText(IDC_EDIT_SEND,""); //发送数据 if(SOCKET_ERROR==WSASendTo(m_socket,&wsabuf,1,&dwSend,0, (SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL)) { MessageBox("发送数据失败!"); return; } }
运行结果
通过域名获得 IP 地址
相关函数:
- 解析域名: gethostbyname
代码示例:
#include <Winsock2.h> #include <iostream> #include <string> #pragma comment(lib,"ws2_32.lib") using namespace std; void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData );//该函数的功能是加载一个Winsocket库版本 if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //解析域名获得 IP 地址 hostent* pHostent = gethostbyname("www.baidu.com"); sockaddr_in sa; ZeroMemory(&sa, sizeof(sa)); //获得 IP 地址 memcpy(&sa.sin_addr.s_addr,pHostent->h_addr_list[0],pHostent->h_length); //将 ID 地址转为字符串形式,输出 IP 地址 string strTemp = inet_ntoa(sa.sin_addr); cout<<strTemp<<endl; //结束套接字库的调用 WSACleanup(); system("pause"); }
运行结果:
在控制台中 ping www.baidu.com 的运行结果: