Socket编程学习————客户端和服务端的编程

由于想学习一下网络爬虫以及拿到的第一份offer涉及到socket编程,这几天初步学习一下Windows环境下的socket编程。

Socket

socket的本质是一种编程接口(API),它是应用层和TCP/IP协议族通信的中介。如果说HTTP是车的外壳,提供了封装或者显示数据的具体形式,那么socket就是发动机,给其提供了网络通信的功能。
在一台连接了互联网的主机上,运行着多个软件,每个软件打开一个socket,并且绑定到一个端口上去,不同的端口对应不同的服务软件。正如socket的英文原意一样,socket就像一个多孔插座,每个插座有不同的编号,不同的插座提供不同的服务。

先放上使用socket的示例图,服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

下面来举例说明:

服务端

#include <cstdio>
#include <Winsock2.h>

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

int  main()
{
    WSADATA wsaData;
    SOCKET sockServer;
    SOCKADDR_IN addrServer;
    SOCKET sockClient;
    SOCKADDR_IN addrClient;
    if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        return 0;
    sockServer = socket(AF_INET, SOCK_STREAM, 0);
    addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//表明对任何IP地址开放
    addrServer.sin_family = AF_INET;
    addrServer.sin_port = htons(6000);
    if(bind(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR)) != SOCKET_ERROR)
        return 0;
    //监听
    listen(sockServer, 200);
    int len = sizeof(SOCKADDR);
    char sendBuf[111];
    char recvBuf[111];

    sockClient = accept(sockServer, (SOCKADDR*)&addrClient, &len);
    recv(sockClient, recvBuf, 111, 0);
    printf("%s\n", recvBuf);
    closesocket(sockClient);
    WSACleanup();
    system("pause");
    return 0;
}

第一步是加载套接字,使用WSAStartup函数加载套接字,函数原型为

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)  

第一个参数wVersionRequested,用来指定准备加载的winsock库的版本。利用MAKEWORD(x,y)宏来赋值。x是高位字节,表示副版本号;y是低位字节,表示主版本号。MAKEWORD(2, 2)表示版本号为2.2。
第二个参数是指向WSADATA结构的指针,是一个返回值,保存了库版本的有关信息。

第二步是创建套接字,使用socket函数,函数原型为

SOCKET socket(int af, int type, int protocol );  

第一个参数用来指定协议族,如对于TCP/IP协议的套接字为AF_INET;
第二个参数用来指定socket类型,SOCK_STREAM指产生流式套接字,SOCK_DGRAM指产生数据报套接字,TCP/IP协议使用SOCK_STREAM。
第三个参数是与特定的地址家族相关的协议,TCP一般为IPPROTO_TCP,也可以写成0,这样系统会根据地址格式和套接字类别自动选择一个合适的协议。

这一步之后要对套接字的各种信息进行赋值,他是一个结构体类型struct sockaddr_id,内部的结构是

struct sockaddr_in{  
    short sin_family;  
    unsigned short sin_port;  
    struct in_addr sin_addr;  
    char sin_zero[8]  
};  

设置服务器端口的时候,会用到htons函数,这个函数把一个u_short类型的值从主机字节顺序转换为TCP/IP网络字节顺序,因为不同的计算机存放多字节的顺序不同。设置服务器IP地址的时候会用到inet_addr函数,它是将点分十进制的IP地址的字符串转换成unsigned long型。inet_ntoa函数做相反的转换。

第三步是绑定,使用bind函数,作用是把创建好的套接字和本机ip以及一个端口绑定,函数原型为:

int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);  

第一个参数是绑定的套接字,
第二个参数指定该套接字的地址信息,这里即服务器的地址信息,它仍是指向struct sockaddr_in类型的结构体的指针,
第三个参数是地址的信息的长度。

第四步为监听连接,使用listen函数,函数原型为:

int listen(SOCKET s, int backlog);  

第一个参数是设置为监听状态的套接字描述字,
第二个参数是等待连接队列的最大长度,超过最大数目的请求会被拒绝。

第五步是接受客户端的连接请求,使用accept函数,函数原型为:

SOCKET accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);  

第一个参数是一个已经设置为监听模式的socket套接字,
第二个参数是一个返回值,用来保存发起连接的客户端的ip地址和端口信息,
第三个参数也是一个返回值,指向一个整型的变量,保存了返回的地址信息的长度。

第六步是打印客户端信息,使用recv函数接收发过来的数据,函数原型为:

int recv(SOCKET s, const char FAR* buf, int len, int flags);  

第一个参数是建立连接的套接字,
第二个参数是指向包含收到的数据的缓冲区的指针,
第三个参数是所指向缓冲区的长度,
第四个参数的值会影响函数的行为,一般置0。

客户端

#include <Winsock2.h>
#include <cstdio>
#pragma comment(lib,"ws2_32.lib")
int main()
{
    WSADATA wsaData;
    SOCKET sockClient;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    sockClient = socket(AF_INET, SOCK_STREAM, 0);
    SOCKADDR_IN addrServer;
    addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    addrServer.sin_family = AF_INET;
    addrServer.sin_port = htons(6000);
    connect(sockClient, (SOCKADDR*)&addrServer, sizeof SOCKADDR);
    char message[] = "Hello!";
    send(sockClient, message, strlen(message) + 1, 0);
    closesocket(sockClient);
    WSACleanup();
    system("pause");
    return 0;
}

唯一不同的是connect函数,函数原型为:

int connect(SOCKET s, const struct sockaddr FAR* name, int namelen);  

第一个参数是建立连接用的套接字,
第二个参数是之前连接的地址信息,
第三个参数是地址的信息长度。

热评文章