socket编程

来源:互联网 发布:培训机构程序员 编辑:程序博客网 时间:2024/06/08 15:40

1、包含文件

#include <WinSock2.h>#pragma comment(lib, "ws2_32.lib")

2、初始化

WSADATA wsa;if(0 != WSAStartup(MAKEWORD(2, 2), &wsa)){    AfxMessageBox(L"套接字初始化失败!");}SOCKET m_hsock;m_hsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(INVALID_SOCKET == m_hsock){    m_hsock = NULL;}

函数原型:
int socket(int domain, int type, int protocol);

参数说明:
domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type:指定Socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。

protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。

3、关闭一个套接口

closesocket(m_hsock);m_hsock = NULL;

4、建立与指定socket的连接

bool Connet(const char* ip, short port){    if(NULL == m_hsock)    {        return false;    }    struct sockaddr_in serverAddress;    memset(&serverAddress, 0, sizeof(sockaddr_in));    serverAddress.sin_family = AF_INET;    serverAddress.sin_addr.S_un.S_addr = inet_addr(ip);    serverAddress.sin_port = htons(port);    if(SOCKET_ERROR == connect(m_hsock, (sockaddr*)&serverAddress, sizeof(serverAddress)))    {        closesocket(m_hsock);        m_hsock = NULL;        return false;    }    return true;}

函数原型: int connect(int s, const struct sockaddr * name, int namelen);

参数:

s:标识一个未连接socket

name:指向要连接套接字的sockaddr结构体的指针

namelen:sockaddr结构体的字节长度

5、在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限

int nNetTimeout = 1000; //1秒setsockopt( m_hsock, SOL_SOCKET, SO_SNDTIMEO, ( char * )&nNetTimeout, sizeof( int ) );//发送时限setsockopt( m_hsock, SOL_SOCKET, SO_RCVTIMEO, ( char * )&nNetTimeout, sizeof( int ) );//接收时限

6、使主动连接套接口变为被连接套接口

bool Listen(const char* ip, short port){    SOCKADDR_IN addr;    memset(&addr, 0, sizeof(SOCKADDR_IN));    addr.sin_family = AF_INET;    addr.sin_addr.S_un.S_addr = inet_addr(ip);    addr.sin_port = htons(port);    int ret = bind(m_hsock, (SOCKADDR*)&addr, sizeof(SOCKADDR));    if(SOCKET_ERROR == ret)    {        AfxMessageBox(L"监听失败!");        closesocket(m_hsock);        m_hsock = NULL;        return false;    }    ret = listen(m_hsock, 5);    if(SOCKET_ERROR == ret)    {        AfxMessageBox(L"failed to listen!");        closesocket(m_hsock);        m_hsock = NULL;        return false;    }    return true;}

bind绑定

函数原型:

int bind(SOCKET socket, const struct sockaddr* address, socklen_t address_len);

参数说明:

socket:是一个套接字描述符。

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

address_len:确定address缓冲区的长度。

返回值:

如果函数执行成功,返回值为0,否则为SOCKET_ERROR。

listen函数

摘要:listen函数使主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

listen函数在一般在调用bind之后-调用accept之前调用,它的函数原型是:

intlisten(int sockfd, int backlog)

参数sockfd

被listen函数作用的套接字,sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时,它是一个主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。

参数backlog

这个参数涉及到一些网络的细节。进程处理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。

毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。

accept函数

摘要:accept()用来接受参数s的socket连接,它的函数原型是:

intaccept(int s,struct sockaddr * addr,int * addrlen)

服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回 INVALID_SOCKET。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。

7、获取socket的对方地址

struct sockaddr_in sa;int len = sizeof(sa);if(!getpeername(m_hsock, (struct sockaddr *)&sa, &len)){printf( "对方IP:%s ", inet_ntoa(sa.sin_addr));printf( "对方PORT:%d ", ntohs(sa.sin_port));}
string GetClientIP(){    SOCKADDR addr = {0};    int len = sizeof(SOCKADDR);    getpeername(m_hsock, &addr, &len);    char peer[128];    unsigned char* paddr = (unsigned char*)&addr;    sprintf(peer,"%d.%d.%d.%d", paddr[4], paddr[5], paddr[6], paddr[7]);    return string(peer);}

补充:getsockname和getpeername调度时机很重要,如果调用时机不对,则无法正确获得地址和端口。

TCP

对于服务器来说,在bind以后就可以调用getsockname来获取本地地址和端口,虽然这没有什么太多的意义。getpeername只有在链接建立以后才调用,否则不能正确获得对方地址和端口,所以他的参数描述字一般是链接描述字而非监听套接口描述字。

对于客户端来说,在调用socket时候内核还不会分配IP和端口,此时调用getsockname不会获得正确的端口和地址(当然链接没建立更不可能调用getpeername),当然如果调用了bind 以后可以使用getsockname。想要正确得到对方地址(一般客户端不需要这个功能),则必须在链接建立以后,同样链接建立以后,此时客户端地址和端口就已经被指定,此时是调用getpeername的时机。

UDP

UDP分为链接和没有链接2种(这个到UDP与connect可以找到相关内容)

没有链接的UDP不能调用getpeername,但是可以调用getsockname,和TCP一样,他的地址和端口不是在调用socket就指定了,而是在第一次调用sendto函数以后

已经链接的UDP,在调用connect以后,这2个函数都是可以用的(同样,getpeername也没太大意义。如果你不知道对方的地址和端口,不可能会调用connect)。

8、有条件的接受客户端连接

CWebsocket* CWebsocket::Accept(int timeout){    struct sockaddr_in client = {0};    int addrlen;    addrlen = sizeof(client);    SOCKET sock = WSAAccept(m_hsock, (SOCKADDR*)&client, &addrlen, NULL, NULL);    int err = GetLastError();    if(INVALID_SOCKET == sock)    {        return NULL;    }    return new CWebsocket(sock);}

函数原型 SOCKET WSAAPI WSAAccept ( SOCKET s, struct sockaddr FAR * addr,

LPINT addrlen,

LPCONDITIONPROC lpfnCondition,

DWORD dwCallbackData

);

参数说明:

s:标识一个套接口的描述字,该套接口在listen()后监听连接。

addr:(可选)指针,存放通讯层所知的连接实体地址的缓冲区的地址。addr参数的具体格式由套接口创建时产生的地址族决定。

addrlen:(可选)指针,存放addr地址长度的整形数的地址。

lpfnCondition:(可选的)用户提供的条件函数的进程实例地址。该函数根据参数传入的调用者信息作出接受或拒绝的决定,并通过给结果参数赋予特定的值来(可选地)创建和/或加入一个套接口组。

dwCallbackData:作为条件函数参数返回给应用程序的回调数据。WinSock不分析该参数。

返回值:

若无错误发生,WSAAccept()函数返回所接受套接口的描述字。否则的话,将返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()来获取相应的错误代码。

addrlen参数引用的整形数初始时包含了addr参数所指向的空间数,在调用返回时包含了返回地址的实际长度。

9、返回对应于给定主机名的包含主机名字和地址信息

string getHostIP(const char* host){    struct hostent *host_entry;    host_entry = gethostbyname(host);    if(NULL != host_entry)    {        char* szLocalIP;        szLocalIP = inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);        return string(szLocalIP);    }    return "";}

10、发送、接收

int Send(const char* pData, int len){    int nByteSend = 0;    nByteSend = send(m_hsock, pData, len, 0);    return nByteSend;}int Recv(char* pData, int len){    int nByteRecv = 0;    nByteRecv = recv(m_hsock, pData, len, 0);    return nByteRecv;}

一、send函数

函数原型:int send( SOCKET s,char *buf,int len,int flags );

功能:不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。

参数一:指定发送端套接字描述符;

参数二:存放应用程序要发送数据的缓冲区;

参数三:实际要发送的数据的字节数;

参数四:一般置为0。

同步Socket的send函数的执行流程,当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度(因为待发送数据是要copy到套接字s的发送缓冲区的,注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里):

1.如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;

2.如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么 send就比较s的发送缓冲区的剩余空间和len:

  (i)如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完;  (ii)如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里。

3.如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

注意:send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)

二、recv函数

函数原型:int recv( SOCKET s, char *buf, int len, int flags)

功能:不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。

参数一:指定接收端套接字描述符;

参数二:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

参数三:指明buf的长度;

参数四 :一般置为0。

同步Socket的recv函数的执行流程:当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,

如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;

如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直
等待,直到协议把数据接收完毕;

当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数;

如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

0 0
原创粉丝点击