Socket函数

|  18.3 | Windows套接字技术

套接字(Socket)是网络通信的基本构件,最初 是由加利福尼亚大学Berkeley学院为UNIX开发的网络通信编程接口,它只能运行在UNIX操作系统,不支持DOS和Windows操作系统。随着 Windows操作系统的日益推广,90年代初,微软和第三方厂商共同制定了一套标准,即Windows Socket规范,简称WinSock。本节将介绍有关Windows套接字的相关技术。

18.3.1  套接字概述

套接字的概念与文件句柄类似,一个套接字就是一个通 信标识,由一个短整数表示,实际上就是一个句柄,代表网络协议中的一组数据,该数据包含了通信双方的IP地址和当前的连接状态等信息。我们知道,如果一个 文件被打开,可以通过文件句柄对文件进行读写操作,套接字也一样,只不过套接字提供的函数更多一些。

套接字存在于通信区域中,由协议、地址、端口来描述 并惟一确定,根据传输协议的不同,套接字可分为3种类型:流式套接字、数据报套接字和原始套接字。流式套接字提供了一个面向连接的、可靠的、数据无错且按 顺序接收的服务,这种套接字对应的是面向连接的传输协议,如TCP/IP协议簇中的TCP。数据报套接字提供了一个无连接服务,不提供无错保证,数据可能 丢失或重复,且接受顺序混乱,该套接字所对应的是无连接传输协议,如TCP/IP协议簇中的UDP。原始套接字允许直接访问低层的协议,如IP、ICMP 协议,该套接字常用于访问服务器中配置的新设备。

18.3.2  WinSock API相关函数

WinSock提供了许多套接字函数,它们并不代表协议的某一个层次,其实质就是一组编程接口,用户利用这些函数可以很容易地进行编程。

(1)socket函数

socket函数用于创建一个套接字。

语法:

SOCKET socket (

  int af,      

  int type,    

  int protocol 

);

af:标识一个地址家族,通常为AF_INET。

type:标识套接字类型,如果为SOCK_STREAM,表示流式套接字;如果为SOCK_DGRAM,表示数据报套接字。

protocol:标识一个特殊的协议被用于这个套接字,通常为0,表示采用默认的TCP/IP协议。

(2)accpet函数

accpet函数用于接受客户端的连接请求。返回值是一个新的套接字,它对应于已经接受的客户端连接,对于该客户端的所有后续操作,都应使用这个新的套接字。

语法:

SOCKET accept (

  SOCKET s,

  struct sockaddr FAR* addr, 

  int FAR* addrlen

);

s:是一个套接字,它应处于监听状态。

addr:是一个sockaddr_in结构指针,包含一组客户端的端口号、IP地址等信息。

addrlen:用于接收参数addr的长度。

(3)bind函数

bind函数用于将套接字绑定到一个已知的地址上。如果函数执行成功,返回值为0,否则为SOCKET_ERROR。

语法:

int bind (

  SOCKET s,                         

  const struct sockaddr FAR*  name, 

  int namelen                       

);

s:是一个套接字。

name:是一个sockaddr结构指针,该结构中包含了要绑定的地址和端口号。

namelen:确定name缓冲区的长度。

在定义一个套接字后,需要调用bind函数为其指定本机地址、协议和端口号。

例如,创建一个套接字s,将其绑定到3010端口上,其代码如下:

int port = 3010;

SOCKET s = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);

sockaddr_in addr;

addr.sin_family = AF_INET; //内部网络协议TCP/UDP等

addr.sin_port = htons(port);  //端口号

addr.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(s,(LPSOCKADDR)&addr,sizeof(addr))==0)

{

  MessageBox("成功");

}

(4)closesocket函数

closesocket函数用于关闭某个套接字。

语法:

int closesocket (

  SOCKET s 

);

s:标识一个套接字。如果参数s设置有SO_DONTLINGER选项,则调用该函数后会立即返回,但此时如果有数据尚未传送完毕,会继续传递数据,然后才关闭套接字。

(5)connect函数

connect函数用于发送一个连接请求。如果函数执行成功,返回值为0,否则为SOCKET_ERROR。用户可以通过WSAGetLastError得到其错误描述。

语法:

int connect (

  SOCKET s,

  const struct sockaddr FAR*  name, 

  int namelen

);

s:标识一个套接字。

name:套接字s想要连接的主机地址和端口号。

namelen:name缓冲区的长度。

(6)htons函数

htons函数将一个16位的无符号短整型数据由主机排列方式转换为网络排列方式。

语法:

u_short htons (

  u_short hostshort 

);

hostshort:一个主机排列方式的无符号短整型数据。

(7)htonl函数

htonl函数将一个32位的无符号长整型数据由主机排列方式转换为网络排列方式。

语法:

u_long htonl (

  u_long hostlong 

);

hostlong:一个主机排列方式的无符号长整型数据。

(8)inet_addr函数

inet_addr函数将一个由字符串表示的地址转换为32位的无符号长整型数据。

语法:

unsigned long inet_addr (

  const char FAR * cp 

);

cp:一个表示IP地址的字符串。

(9)listen函数

listen函数用于将套接字置入监听模式。

语法:

int listen (

  SOCKET s,

  int backlog 

);

s:套接字。

backlog:表示等待连接的最大队列长度。例如,如果backlog被设置为3,此时有4个客户端同时发出连接请求,那么前3个客户端连接会放置在等待队列中,第4个客户端会得到错误信息。

(10)recv函数

recv函数用于从连接的套接字中返回数据。

语法:

int recv (

  SOCKET s,      

  char FAR* buf, 

  int len,       

  int flags      

);

recv函数参数的说明如表18.5所示。

表18.5                           recv函数参数说明

参 数 名 称

参 数 描 述

S

标识一个套接字

Buf

是接收数据的缓冲区

Len

是buf的长度

Flags

表示函数的调用方式,可选值如下:

MSG_PEEK_用来查看传来的数据,在序列前端的数据会被复制一份到返回缓冲区中,但是这个数据不会从序列中移走

MSG_OOB_用来处理Out-Of-Band数据

(11)select函数

select函数用来检查一个或多个套接字是否处于可读、可写或错误状态。

语法:

int select (

  int nfds,

  fd_set FAR * readfds,              

  fd_set FAR * writefds,             

  fd_set FAR * exceptfds,            

  const struct timeval FAR * timeout 

);

select函数参数说明如表18.6所示。

表18.6                          select函数参数说明

参 数 名 称

参 数 描 述

nfds

没有实际意义,只是为了和UNIX下的套接字兼容

readfds

标识一组被检查可读的套接字

writefds

标识一组被检查可写的套接字

exceptfds

是被检查有错误的套接字

timeout

标识函数的等待时间

(12)send函数

send函数在已经建立连接的套接字上发送数据。

语法:

int send (

  SOCKET s,             

  const char FAR * buf, 

  int len,              

  int flags             

);

send函数参数说明如表18.7所示。

表18.7                           send函数参数说明

参 数 名 称

参 数 描 述

s

标识一个套接字

buf

是存放要发送数据的缓冲区

len

标识缓冲区长度

flags

标识函数的调用方式

(13)WSAStartup函数

WSAStartup函数用于初始化WS2_32动态库。它应该是应用程序第1个调用的Windows Socket函数,用于确定Windows Socket使用的版本。

语法:

int WSAStartup (

  WORD wVersionRequested, 

  LPWSADATA lpWSAData 

);

wVersionRequested:标识调用者使用的Windows Socket的版本,高字节记录修订版本,低字节记录主版本。例如,如果Windows Socket的版本为2.1,则高字节记录1,低字节记录2。

lpWSAData:记录Windows Socket的详细信息。

下面的代码用于确定Windows Socket使用的版本。

WSADATA wsd;

WSAStartup(MAKEWORD(2,2),&wsd);

(14)WSACleanup函数

WSACleanup函数与WSAStartup函数是相对的,用于终止使用WS2_32动态库。

语法:

int  WSACleanup (void);

说明:

当Windows Socket使用完成时,应调用WSACleanup函数释放分配给应用程序或动态库的资源。

(15)WSAAsyncSelect函数

WSAAsyncSelect函数用于将网络中发生的事件关联到窗口的某个消息中。

语法:

int WSAAsyncSelect (

  SOCKET s,

  HWND hWnd,

  unsigned int wMsg,

  long lEvent

);

WSAAsyncSelect函数参数说明如表18.8所示。

表18.8                      WSAAsyncSelect函数参数说明

参 数 名 称

参 数 描 述

s

标识套接字

hWnd

标识窗口句柄

wMsg

标识窗口消息

lEvent

标识网络中的事件

18.3.3  使用套接字函数设计网络聊天室

在18.3.2节中笔者介绍了常用的套接字函数,接下来将利用这些套接字函数设计一个网络聊天室程序。程序由两个实例组成,第1个实例为服务器端,负责接收用户的连接请求,并转发用户信息。第2个实例为客户端,负责连接服务器并发送信息。

服务器端程序设计步骤如下:

  ch1806 实例位置:mr/18/sl/06

(1)创建一个基于对话框的应用程序,设计对话框资源如图18.11所示。

(2)在对话框的头文件中引用“winsock2.h”头文件,并导入网络库文件。

#include "winsock2.h"

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

(3)在应用程序的InitInstance方法中初始化套接字。

WSADATA wsd;

WSAStartup(MAKEWORD(2,2),&wsd);

(4)在对话框类中定义成员变量,记录服务器套接字和与之连接的客户端套接字信息。

SOCKET m_server,m_client;

SOCKET m_Clients[MAXNUM];      //客户端套接字

int    m_CurClient;            //当前连接的客户数量

(5)在对话框类的OnInitDialog方法中创建套接字,并初始化数据。

//创建套接字

m_server = socket(AF_INET,SOCK_STREAM,0);

//将网络中的事件关联到窗口的消息函数中

WSAAsyncSelect(m_server,m_hWnd,20000,FD_WRITE|FD_READ|FD_ACCEPT);

m_client = 0;

m_serverIP = "";

for (int i = 0; i< MAXNUM;i++)

    m_Clients[i]= 0;

m_CurClient = 0;

(6)处理“监听”按钮的单击事件,将套接字绑定到本机地址,并开始监听套接字。

void CServerDlg::OnOK()

{

    //服务器端地址

    sockaddr_in serveraddr;

    serveraddr.sin_family = AF_INET;

    m_IP.GetWindowText(m_serverIP);

    //设置本机地址

    serveraddr.sin_addr.S_un.S_addr  = inet_addr(m_serverIP);

    UpdateData(TRUE);

    //设置端口号

    serveraddr.sin_port = htons(m_port);

    //绑定地址

    if (bind(m_server,(sockaddr*)&serveraddr,sizeof(serveraddr)))

    {

        MessageBox("绑定地址失败.");

        return;

    }

    //开始监听

    listen(m_server,50);

}

(7)向对话框中添加HandleData方法,用于接受客户端的连接,并获得客户端传来的数据,将其转发给其他客户端。

void CServerDlg::HandleData()

{   

    sockaddr_in serveraddr;

    char buffer[1024];

    int len =sizeof(serveraddr);

 

    //接收客户端的数据

    int curlink = -1;

    int num = -1;

    for (int p = 0;p< MAXNUM; p++)

    {

        num= recv(m_Clients[p],buffer,1024,0);

        if (num != -1)

        {

            curlink = p;

            break;

        }

    }

    buffer[num]= 0;

    if (num==-1) //接受客户端的连接

    {

        if (m_CurClient < MAXNUM)

        {

            m_Clients[m_CurClient] = accept(m_server,(struct sockaddr*)

            &serveraddr,&len);

            m_CurClient+=1;

        }   

        return;

    }

    //将接收的数据发送给客户端

    for (int j = 0;j< m_CurClient;j++)

        if (j != curlink)

        send(m_Clients[j],buffer,num,0);

}

(8)改写对话框类的PreTranslateMessage方法,在服务器套接字中有事件触发时调用HandleData方法。

BOOL CServerDlg::PreTranslateMessage(MSG* pMsg)

{

    if (pMsg->message==20000)

    {

        HandleData();

        return TRUE;

    }

    else

        return CDialog::PreTranslateMessage(pMsg);

}

客户端程序设计步骤如下。

  ch1807 实例位置:mr/18/sl/07

(1)创建一个基于对话框的工程,设置对话框资源如图18.12所示。

图18.12  客户端设计窗口

(2)在对话框类的头文件中引用“winsock2.h”头文件,并导入“ws2_32.lib”库文件。

#include "winsock2.h"

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

(3)在应用程序的InitInstance方法中初始化套接字。

WSADATA wsd;

WSAStartup(MAKEWORD(2,2),&wsd);

(4)在对话框的OnInitDialog方法中创建套接字。

m_client = socket(AF_INET,SOCK_STREAM,0);

(5)处理“连接”按钮的单击事件,连接服务器,并设置套接字接收数据时触发的消息。

void CClientDlg::OnOK()

{

    //服务器端地址

    sockaddr_in serveraddr;

    UpdateData(TRUE);

    serveraddr.sin_family = AF_INET;

    serveraddr.sin_port = htons(m_port);

    serveraddr.sin_addr.S_un.S_addr = inet_addr(m_IP);

    if (connect(m_client,(sockaddr*)&serveraddr,sizeof(serveraddr))!=0)

    {

        MessageBox("连接失败");

        return;

    }   

    else

        MessageBox("连接成功");

    WSAAsyncSelect(m_client,m_hWnd,1000,FD_READ);

    CString str,info ;

    m_name.GetWindowText(str);

    info.Format("%s------>%s",str,"进入聊天室");

    int i = send(m_client,info.GetBuffer(0),info.GetLength(),0);

}

(6)处理“发送”按钮的单击事件,向服务器发送数据。

void CClientDlg::OnButton1()

{

    CString str,name,info ;

    m_name.GetWindowText(name);

    m_info.GetWindowText(str);

    if (!name.IsEmpty()&&!str.IsEmpty())

    {

        info.Format("%s说: %s",name,str);

        //开始发送数据

        int i = send(m_client,info.GetBuffer(0),info.GetLength(),0);   

        m_list.AddString(info);

        m_info.SetWindowText("");

    }

}

(7)向对话框类中添加ReceiveData方法,用于接收从服务器传来的数据。

void CClientDlg::ReceiveData()

{

    char buffer[1024];

    //接收服务器端传来的数据,

    int num = recv(m_client,buffer,1024,0);

    buffer[num] = 0;

    //将接收的数据添加到列表框中

    m_list.AddString(buffer);

}

(8)改写对话框的PreTranslateMessage方法,截获对话框的消息,用于接收数据。

BOOL CClientDlg::PreTranslateMessage(MSG* pMsg)

{

    if (pMsg->message==1000)

    {

        ReceiveData();

        return TRUE;

    }

    else

        return CDialog::PreTranslateMessage(pMsg);

}

运行程序,效果如图18.13、图18.14、图18.15所示。

图18.13  聊天室服务器

   

图18.14  客户端窗口1                           图18.15  客户端窗口2

你可能感兴趣的:(Socket函数)