Socket套接字
寻址方式和字节顺序
寻址方式
Winsock(Socket API)中,套接字地址结构定义如下:
struct sockaddr_in{
short sin_family;//指定地址家族即地址格式。TCP/IP设置为AF_INET。
unsignedshort sin_port;//端口号码。
struct in_addr sin_addr;//IP地址。
char sin_zero[8];//留作备用,需要指定为0。
};
其中结构成员变量sin_addr表示32位的IP地址结构,定义如下:
struct in_addr{
union{
struct{
unsigned char s_b1, s_b2, s_b3, s_b4;
}S_un_b;
struct{
unsigned short s_w1, s_w2;
}S_un_w;
unsigned long S_addr;
}S_un;
};
字节顺序
1、字节顺序格式
网络字节顺序(大端格式):低地址存放高位字节,高地址存放低位字节;
主机字节顺序(小端格式):低地址存放低字节,高地址存放高字节。
2、字节顺序转换函数
1.u_short htons(u_short hostshort);//将一个u_short类型的IP地址从主机字节顺序转换到网络字节顺序。
2.u_long htonl(u_long hostlong);//将一个u_long类型的IP地址从主机字节顺序转换到网络字节顺序。
3.u_long ntohl(u_long netlong);//将一个u_long类型的IP地址从网络字节顺序转换到主机字节顺序。
4.u_short ntohs(u_short netshort);//将一个u_short类型的IP地址从网络字节顺序转换到主机字节顺序。
5.unsigned long inet_addr(const char FAR *cp);//将一个字符串IP转换到以网络字节顺序排列的IP地址。
6.char FAR* inet_ntoa(struct in_addr in);//将一个以网络字节顺序排列的IP地址转换为一个字符串IP。
MFC中CSocket类中相关函数
1.创建套接字
使用afxsock.h下的CSocket类创建套接字对象本质是调用该类的构造函数进行创建,原型如下:
CSocket::CSocket();
如果用户需要创建套接字对象指针,则应该使用new关键字进行创建,代码如下:
CSocket *sock;//定义套接字指针对象。
sock=new CSocket;//使用new关键字创建套接字对象,返回对象的地址。
2.绑定地址信息
调用该类的函数Bind()将套接字对象与服务器地址信息绑定在一起。函数原型如下:
BOOL Bind(const SOCKADDR* lpSockAddr, intnSockAddrLen);
如果函数调用成功,则返回true;否则,返回false。
1.参数lpSockAddr指定将要绑定的服务器地址结构(指针可变,数据不可变);
2.参数nSockAddrLen表示地址结构的长度。
在服务器端,当地址信息绑定套接字成功后,还需要调用函数Listen()在指定端口监听客户端的连接请求。Listen()函数原型如下:
BOOL Listen(int nConnectionBacklog = 5);
参数nConnectionBacklog表示套接字监听客户端请求的最大数目,该参数有效范围是[1,5],默认为5,表示该套接字只能监听5个客户端所发送的连接请求。
3.连接服务器
客户端创建套接字成功以后,可以调用函数Connect()向服务器发送连接请求,函数原型如下:
BOOL Connect(const SOCKADDR* lpSockAddr,int nSockAddrLen);
函数调用成功后,返回true;否则,返回false。
1.参数lpSockAddr表示将连接的服务器地址结构。
2.参数nSockAddrLen表示地址结构的长度大小。
4.数据交换
无论是服务器,还是客户端都是通过函数Send()和Receive()进行数据交换,函数原型如下:
virtual int Send(const void* lpBuf, intnBufLen, int nFlags = 0);
virtual int Receive(void* lpBuf, intnBufLen, int nFlags = 0);
函数Send()用于发送指定缓冲区的数据;
函数Receive()用于接收对方发送的数据,并将数据存放在指定缓冲区中。
1.参数lpBuf表示数据缓冲区地址;
2.参数nBufLen表示数据缓冲区的大小;
3.参数nFlags表示数据发送或接收的标志,一般情况下,该参数均设置为0。
5.关闭套接字对象
当服务器和客户端的通信完成以后,用户还必须调用函数Close()将套接字对象关闭。否则,程序可能在退出时发生错误。该函数原型如下:
virtual void Close();
Winsock编程
1.初始化和释放套接字库
所有的Winsock函数均是从动态链接库WS2_32.DLL中导出的,在头文件下边使用#pragma comment(lib, “LibName.lib”)显式链接LibName.lib库。语句如下:
#pragma comment(lib, “ws2_32.lib”) //链接ws2_32.lib库,没有分号。
调用WSAStartup()对该库进行初始化,函数原型如下:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
该函数调用成功,将返回0;否则,调用函数失败。
1.参数wVersionRequested表示当前套接字库的版本号;
2.参数lpWSAData指向结构体WSADATA的指针变量,表示获取到的套接字详细信息。
程序退出时,用户还应该调用函数WSACleanup()释放该套接字库,如下:
::WSACleanup();
2.创建套接字句柄
在Socket API中,创建套接字句柄的函数是socket()。函数原型如下:
SOCKET socket(int af, int type, intprotocol);
1.参数af指定套接字所使用的地址格式,本章只能设置成AF_INET;
2.参数type表示套接字类型,SOCK_STREAM表示流式套接字(TCP),SOCK_DGRAM表示数据包套接字(UDP),SOCK_RAW表示原始套接字(未提及);
3.参数protocol:如果参数type指定TCP或UDP,该参数可以设置为0。
3.绑定地址信息
对于服务器而言,套接字创建成功后,还应该使用bind()函数将套接字与地址结构信息相关联,函数原型如下:
int bind(SOCKET s, const struct sockaddrFAR* name, int namelen);
1.参数s表示套接字句柄;
2.参数name表示地址结构信息的变量指针;
3.参数namelen表示地址结构的大小。
函数调用成功,则返回0;否则函数调用失败。
当服务器程序将套接字句柄绑定套接字地址成功后,调用函数listen()实现监听端口的功能,函数原型如下:
int listen(SOCKET s, int backlog);
1.参数s表示实现监听功能的套接字句柄;
2.参数backlog指定监听的最大连接数量。
注:该函数仅被用于流式套接字,如果多个客户端同时向服务器发送连接请求,并且超过了最大监听数,则客户端将返回错误代码。
4.连接
客户端程序连接服务器使用函数connect()实现,函数原型如下:
int connect(SOCKET s, const struct sockaddrFAR* name, int namelen);
1.参数s表示套接字句柄;
2.参数name表示将要连接的服务器地址结构信息的变量指针;
3.参数namelen表示地址结构的大小。
如果服务器接收到客户端的连接请求,则调用函数accept()接受该请求,函数原型如下:
SOCKET accept(SOCKET s, struct sockaddrFAR* addr, int FAR* addrlen);
1.参数s表示套接字句柄;
2.参数addr表示地址结构信息的指针变量;
3.参数addrlen表示地址结构的大小。
如果函数调用成功,则返回一个新的套接字句柄,用于通信双方数据的传输。
5.数据收发
调用函数send()和recv()进行数据的发送和接收,函数原型如下:
int send(SOCKET s, const char FAR* buf, intlen, int flags);
int recv(SOCKET s, char FAR* buf, int len,int flags);
注:
1.如果服务器使用上面的函数进行数据手法,则参数s应该为监听函数返回的新套接字句柄;如果客户端使用以上函数进行数据收发,则参数s应该为客户端创建的套接字句柄。
2.send函数仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里,copy数据成功后,返回copy的字节数。
3.recv函数仅仅是把s的接收缓冲中的协议接收的数据copy到buf中,返回值为copy的字节数。即返回值<0:出错;返回值=0:连接关闭;返回值>0:接收的字节数。
6.关闭套接字
当套接字使用完毕或程序退出时,用户应该调用函数closesocket()关闭套接字句柄,函数原型如下:
int closesocket(SOCKET s);
参数s表示即将关闭的套接字句柄。
7.服务器端源码
#include <Winsock2.h>
#include <cstdio>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
/*加载winsock库*/
WSADATAwsaData;
//参数:版本号,指向WSADATA结构体的指针
WSAStartup(MAKEWORD(2,2), &wsaData);
SOCKETsockData;
/*构造监听socket,流式socket
*参数:套接字所使用的地址格式,套接字类型,协议编号
*AF_INET是IPv4 网络协议的套接字类型
*/
SOCKETsockServer = socket(AF_INET, SOCK_STREAM, 0);
/*配置监听地址和端口*/
SOCKADDR_INaddrServer;
addrServer.sin_port= htons(6000); //绑定端口6000
addrServer.sin_addr.S_un.S_addr= htonl(INADDR_ANY);//INADDR_ANY表示任何IP,传递unsigned long类型的IP地址
addrServer.sin_family= AF_INET; //指定地址格式,表示TCP/IP格式
/*绑定监听socket*/
bind(sockServer,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));
//Listen监听端
listen(sockServer,5);//5为等待连接数目
cout<< "服务器已启动!" << endl <<"监听中..." << endl;
intlen = sizeof(SOCKADDR);
charsendBuf[100] = "hello!";//发送至客户端的字符串
charrecvBuf[100];//接受客户端返回的字符串
//阻塞进程,等待客户端接入,直到有客户端连接上来为止
sockData= accept(sockServer, (SOCKADDR*)&addrServer, &len);
while(1){
//接收并打印客户端数据
recv(sockData,recvBuf, sizeof(recvBuf), 0);
if(strcmp(recvBuf, "q") == 0){
cout<< "对方已关闭连接!" << endl;
break;
}
cout<< "recv:" << recvBuf << endl;
//输入并发送数据
cout<< "send:";
cin>> sendBuf;
send(sockData,sendBuf, strlen(sendBuf) + 1, 0);
if(strcmp(sendBuf, "q") == 0){
cout<< "主动关闭连接!" << endl;
break;
}
}
//关闭socket
closesocket(sockData);
closesocket(sockServer);
//释放winsock库
WSACleanup();
return0;
}
8.客户端源码
#include <Winsock2.h>
#include <cstdio>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
/*加载winsock库*/
WSADATAwsaData;
//MAKEWORD(2,2)生成WORD类型参数,表示版本为2.2
WSAStartup(MAKEWORD(2,2), &wsaData);
//新建客户端通讯socket,流式socket
SOCKETsockClient = socket(AF_INET, SOCK_STREAM, 0);
//定义要连接的服务端地址
SOCKADDR_INaddrServer; //服务端地址
addrServer.sin_port= htons(6000);//连接端口6000
addrServer.sin_addr.S_un.S_addr= inet_addr("127.0.0.1");//目标IP(127.0.0.1是回送地址)
addrServer.sin_family= AF_INET;
//连接到服务端
connect(sockClient,(SOCKADDR*)&addrServer, sizeof(SOCKADDR));
charsendBuf[20]= "Hello Socket!";
charrecvBuf[20];
do{
//发送数据
send(sockClient,sendBuf, strlen(sendBuf) + 1, 0);
if(strcmp(sendBuf, "q") == 0){
cout<< "主动关闭连接!" << endl;
break;
}
//接收数据
recv(sockClient,recvBuf, sizeof(recvBuf), 0);
if(strcmp(recvBuf, "q") == 0){
cout<< "对方已关闭连接!" << endl;
break;
}
cout<< "recv:" << recvBuf << endl;
cout<< "next send:";
cin>> sendBuf;
}while (1);
//关闭socket
closesocket(sockClient);
//释放winsock
WSACleanup();
return0;
}