socket接口详解

1. socket概述

socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操做抽象为几个简单的接口供应用层调用已实现进程在网络中通讯。html

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在创建链接打开后,能够向本身文件写入内容供对方读取或者读取对方内容,通信结束时关闭文件。编程

2.接口详解

socket():建立socketwindows

bind():绑定socket到本地地址和端口,一般由服务端调用缓存

listen():TCP专用,开启监听模式服务器

accept():TCP专用,服务器等待客户端链接,通常是阻塞态网络

connect():TCP专用,客户端主动链接服务器数据结构

send():TCP专用,发送数据dom

recv():TCP专用,接收数据socket

sendto():UDP专用,发送数据到指定的IP地址和端口tcp

recvfrom():UDP专用,接收数据,返回数据远端的IP地址和端口

closesocket():关闭socket

2.1 socket()

原型:int socket (int domain, int type, int protocol)

功能描述:初始化建立socket对象,一般是第一个调用的socket函数。 成功时,返回非负数的socket描述符;失败是返回-1。socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用socket()函数时,socket执行体将创建一个socket,实际上"创建一个socket"意味着为一个socket数据结构分配存储空间。socket执行体为你管理描述符表。

参数解释

domain -- 指明使用的协议族。经常使用的协议族有,AF_INET、AF_INET六、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通讯中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名做为地址。

type -- 指明socket类型,有3种:

                                                 SOCK_STREAM -- TCP类型,保证数据顺序及可靠性;

                                                 SOCK_DGRAM --  UDP类型,不保证数据接收的顺序,非可靠链接;

                                                 SOCK_RAW -- 原始类型,容许对底层协议如IP或ICMP进行直接访问,不太经常使用。

protocol -- 一般赋值"0",由系统自动选择。

2.2 bind()

原型:int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen)

功能描述:将建立的socket绑定到指定的IP地址和端口上,一般是第二个调用的socket接口。返回值:0 -- 成功,-1 -- 出错。当socket函数返回一个描述符时,只是存在于其协议族的空间中,并无分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数能够将一组固定的地址绑定到sockfd上。

一般服务器在启动的时候都会绑定一个众所周知的协议地址,用于提供服务,客户就能够经过它来接连服务器;而客户端能够指定IP或端口也能够都不指定,未分配则系统自动分配。这就是为何一般服务器端在listen以前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

注意

(1) 若是有多个可用的链接(多个IP),内核会根据优先级选择一个IP做为源IP使用。

(2) 若是socket使用bind绑定到特定的IP和port,则不管是TCP仍是UDP,都会从指定的IP和port发送数据。

参数解释

sockfd -- socket()函数返回的描述符;

myaddr -- 指明要绑定的本地IP和端口号,使用网络字节序,即大端模式(详见3.1)。

addrlen -- 常被设置为sizeof(struct sockaddr)。

能够利用下边的赋值语句,自动绑定本地IP地址和随机端口:

my_addr.sin_port = 0;    /* 系统随机选择一个未被使用的端口号 */ 
my_addr.sin_addr.s_addr = INADDR_ANY;   /* 填入本机IP地址 */ 

另外要注意的是,当调用函数时,通常不要将端口号置为小于1024的值,由于1~1024是保留端口号,你可使用大于1024中任何一个没有被占用的端口号。

2.3 listen()

原型:int listen(int sockfd, int backlog)

功能描述:listen()函数仅被TCP类型的服务器程序调用,实现监听服务,它实现2件事情:

“1. 当socket()建立1个socket时,被假设为主动式套接字,也就是说它是一个将调用connect()发起链接请求的客户端套接字;函数listen()将套接口转换为被动式套接字,指示内核接受向此套接字的链接请求,调用此系统调用后tcp 状态机由close转换到listen。
   2.第2个参数指定了内核为此套接字排队的最大链接个数。”

listen()成功时返回0,错误时返回-1。

参数解释

sockfd -- socket()函数返回的描述符;

backlog -- 指定内核为此套接字维护的最大链接个数,包括“未完成链接队列--未完成3次握手”、“已完成链接队列--已完成3次握手,创建链接”。大多数系统缺省值为20。

2.4 accept()

原型: int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)

功能描述:accept()函数仅被TCP类型的服务器程序调用,从已完成链接队列返回下一个创建成功的链接,若是已完成链接队列为空,线程进入阻塞态睡眠状态。成功时返回套接字描述符,错误时返回-1。

若是accpet()执行成功,返回由内核自动生成的一个全新socket描述符,用它引用与客户端的TCP链接。一般咱们把accept()第一个参数成为监听套接字(listening socket),把accept()功能返回值成为已链接套接字(connected socket)。一个服务器一般只有1个监听套接字,监听客户端的链接请求;服务器内核为每个客户端的TCP链接维护1个已链接套接字,用它实现数据双向通讯。

参数解释

sockfd -- socket()函数返回的描述符;

addr -- 输出一个的sockaddr_in变量地址,该变量用来存放发起链接请求的客户端的协议地址;

addrten -- 做为输入时指明缓冲器的长度,做为输出时指明addr的实际长度。

2.5 connetct()

原型: int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)

功能描述:connect()一般由TCP类型客户端调用,用来与服务器创建一个TCP链接,实际是发起3次握手过程,链接成功返回0,链接失败返回1。

注意

(1) 能够在UDP链接使用使用connect(),做用是在UDP套接字中记住目的地址和目的端口。
(2) UDP套接字使用connect后,若是数据报不是connect中指定的地址和端口,将被丢弃。没有调用connect的UDP套接字,将接收全部到达这个端口的UDP数据报,而不区分源端口和地址。

参数解释

sockfd -- 本地客户端额socket描述符;

serv_addr -- 服务器协议地址;

addrlen -- 地址缓冲区的长度。

2.6 send()

原型:int send(int sockfd, const void *msg, int len, int flags)

功能描述:TCP类型的数据发送。

每一个TCP套接口都有一个发送缓冲区,它的大小能够用SO_SNDBUF这个选项来改变。调用send函数的过程,实际是内核将用户数据拷贝至TCP套接口的发送缓冲区的过程:若len大于发送缓冲区大小,则返回-1;不然,查看缓冲区剩余空间是否容纳得下要发送的len长度,若不够,则拷贝一部分,并返回拷贝长度(指的是非阻塞send,若为阻塞send,则必定等待全部数据拷贝至缓冲区才返回,所以阻塞send返回值一定与len相等);若缓冲区满,则等待发送,有剩余空间后拷贝至缓冲区;若在拷贝过程出现错误,则返回-1。关于错误的缘由,查看errno的值。

若是send在等待协议发送数据时出现网络断开的状况,则会返回-1。注意:send成功返回并不表明对方已接收到数据,若是后续的协议传输过程当中出现网络错误,下一个send便会返回-1发送错误。TCP给对方的数据必须在对方给予确认时,方可删除发送缓冲区的数据。不然,会一直缓存在缓冲区直至发送成功(TCP可靠数据传输决定的)。

参数解释

sockfd -- 发送端套接字描述符(非监听描述符)。

msg -- 待发送数据的缓冲区。

len -- 待发送数据的字节长度。

flags -- 通常状况下置为0。

2.7 recv()

原型:int recv(int sockfd, void *buf, int len, unsigned int flags)

功能描述:TCP类型的数据接收。

recv()从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休眠状态。若非阻塞,则当即返回,有数据则返回拷贝的数据大小,不然返回错误-1。

参数解释

sockefd -- 接收端套接字描述符(非监听描述符);

buf -- 接收缓冲区的基地址;

len -- 以字节计算的接收缓冲区长度;

flags -- 通常状况下置为0。

2.8 sendto()

原型:int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *dst_addr, int addrlen)

功能描述用于非可靠链接(UDP)的数据发送,由于UDP方式未创建链接socket,所以须要制定目的协议地址。

当本地与不一样目的地址通讯时,只需指定目的地址,可以使用同一个UDP套接口描述符sockfd,而TCP要预先创建链接,每一个链接都会产生不一样的套接口描述符,体如今:客户端要使用不一样的fd进行connect,服务端每次accept产生不一样的fd。

由于UDP没有真正的发送缓冲区,由于是不可靠链接,没必要保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。所以它不须要一个发送缓冲区。写UDP套接口的sendto/write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,若是输出队列没有足够的空间存放数据,将返回错误ENOBUFS.

参数解释

sockfd -- 发送端套接字描述符(非监听描述符);

msg -- 待发送数据的缓冲区;

len -- 待发送数据的字节长度;

flags -- 通常状况下置为0;

dst_addr -- 数据发送的目的地址;

addrlen -- 地址长度。

2.9 recvfrom()

原型:int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, int*fromlen)

功能描述:用于非可靠链接(UDP)的数据接收。

参数解释

sockfd -- 接收端套接字描述;

buf -- 用于接收数据的应用缓冲区地址;

len -- 指名缓冲区大小;

flags -- 一般为0;

src_addr -- 数据来源端的地址;

fromlen -- 做为输入时,fromlen常置为sizeof(struct sockaddr);当输出时,fromlen包含实际存入buf中的数据字节数。

3.TCP例程

给出1个TCP类型socket通讯的源代码,运行于windows平台。

3.1服务器端源码

 

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>

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

int main(int argc, char* argv[])
{
    //初始化WSA
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA wsaData;
    if(WSAStartup(sockVersion, &wsaData)!=0)
    {
        return 0;
    }

    //建立套接字
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(slisten == INVALID_SOCKET)
    {
        printf("socket error !");
        return 0;
    }

    //绑定IP和端口
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);
    sin.sin_addr.S_un.S_addr = INADDR_ANY; 
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf("bind error !");
    }

    //开始监听
    if(listen(slisten, 5) == SOCKET_ERROR)
    {
        printf("listen error !");
        return 0;
    }

    //循环接收数据
    SOCKET sClient;
    sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    char revData[255]; 
    while (true)
    {
        printf("等待链接...\n");
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
        if(sClient == INVALID_SOCKET)
        {
            printf("accept error !");
            continue;
        }
        printf("接受到一个链接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
        
        //接收数据
        int ret = recv(sClient, revData, 255, 0);        
        if(ret > 0)
        {
            revData[ret] = 0x00;
            printf(revData);
        }

        //发送数据
        char * sendData = "你好,TCP客户端!\n";
        send(sClient, sendData, strlen(sendData), 0);
        closesocket(sClient);
    }
    
    closesocket(slisten);
    WSACleanup();
    return 0;
}

 

3.2 客户端源码

#include "stdafx.h"
#include <WINSOCK2.H>
#include <STDIO.H>

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


int main(int argc, char* argv[])
{
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data; 
    if(WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }

    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET)
    {
        printf("invalid socket !");
        return 0;
    }

    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(8888);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error !");
        closesocket(sclient);
        return 0;
    }
    char * sendData = "你好,TCP服务端,我是客户端!\n";
    send(sclient, sendData, strlen(sendData), 0);

    char recData[255];
    int ret = recv(sclient, recData, 255, 0);
    if(ret > 0)
    {
        recData[ret] = 0x00;
        printf(recData);
    }
    closesocket(sclient);
    WSACleanup();
    return 0;
}

4.UDP例程

4.1 服务器端源码

 

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>

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

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    WORD sockVersion = MAKEWORD(2,2);
    if(WSAStartup(sockVersion, &wsaData) != 0)
    {
        return 0;
    }

    SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
    if(serSocket == INVALID_SOCKET)
    {
        printf("socket error !");
        return 0;
    }

    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(8888);
    serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("bind error !");
        closesocket(serSocket);
        return 0;
    }
    
    sockaddr_in remoteAddr;
    int nAddrLen = sizeof(remoteAddr); 
    while (true)
    {
        char recvData[255];  
        int ret = recvfrom(serSocket, recvData, 255, 0, (sockaddr *)&remoteAddr, &nAddrLen);
        if (ret > 0)
        {
            recvData[ret] = 0x00;
            printf("接受到一个链接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
            printf(recvData);            
        }

        char * sendData = "一个来自服务端的UDP数据包\n";
        sendto(serSocket, sendData, strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);    

    }
    closesocket(serSocket); 
    WSACleanup();
    return 0;
}

 

4.2 客户端源码

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>

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

int main(int argc, char* argv[])
{
    WORD socketVersion = MAKEWORD(2,2);
    WSADATA wsaData; 
    if(WSAStartup(socketVersion, &wsaData) != 0)
    {
        return 0;
    }
    SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);
    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    int len = sizeof(sin);
    
    char * sendData = "来自客户端的数据包.\n";
    sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);

    char recvData[255];     
    int ret = recvfrom(sclient, recvData, 255, 0, (sockaddr *)&sin, &len);
    if(ret > 0)
    {
        recvData[ret] = 0x00;
        printf(recvData);
    }

    closesocket(sclient);
    WSACleanup();
    return 0;
}

5. 参考资料

1. 经常使用socket函数详解

2. 网络编程socket基本API详解

3. VC++ socket通讯实例详解

相关文章
相关标签/搜索