C语言Socket实现网络通信

document.txt
*套接字通信流程
Winsock库的加载和卸载
要使用Windows Socket API进行编程,首先必须调用WSAStartup()函数初始化Winsock动态库。
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
参数一wVersionRequested:为我们要求初始化的Winsock版本号
参数二lpWSAData:为实际初始化成功的WSA(Windows Socket API)版本信息。
在程序末尾,需调用int WSACleanup(void)函数卸载Winsock动态库。
套接字的创建和释放
首先必须调用socket()函数创建一个套接字描述符
// The socket function creates a socket that is bound to a specific service provider.
SOCKET socket(int af,// [in] Address family specification.
int type,// [in] Type specification for the new socket.
int protocol// [in] Protocol to be used with the socket that is specific to the indicated address family.
);
套接字的释放
当不使用socket()创建的套接字时,应该调用closesocket()函数将它关闭
// The closesocket function closes an existing socket.
int closesocket(
SOCKET s// [in] Descriptor identifying the socket to close.
);
绑定套接字到指定的IP地址和端口
对于传输套接字,在执行收发数据前需要对本地端口进行绑定
// The bind function associates a local address with a socket.
int bind(
SOCKET s, // [in] Descriptor identifying an unbound socket.
const struct sockaddr FAR *name, // [in] Address to assign to the socket from the SOCKADDR structure.
int namelen // [in] Length of the value in the name parameter.
);
bind()函数用在套接字连接建立之前,它的作用是绑定面向连接(connection oriented)的或者面向无连接
(transaction oriented)的套接字。当一个套接字被socket函数创建以后,它存在于指定的地址家族里,
但是它是匿名的。bind()函数通过安排一个本地名称到未命名的socket建立此socket的本地关联。
本地名称包含3个部分:主机地址、协议号(TCP或UDP)和端口号。
TCP服务器设置套接字进入监听状态
// The listen function places a socket a state where it is listening for an incoming connection.
int listen(
SOCKET s,// [in] Descriptor identifying a bound, unconnected socket.
int backlog// [in] Maximum length of the queue of pending connections.
);
服务器为了接受连接,首先使用socket()函数创建一个套接字,然后使用bind()函数将它绑定到一个本地地址(端口),
再用listen()函数为到达的连接指定一个backlog。
backlog参数指定了正在等待连接的最大队列长度。
客户端主动连接
// The connect function establishes a connection to a specified socket.
int connect(
SOCKET s,// [in] Descriptor identifying an unconnected socket.
const struct sockaddr FAR *name,// [in] Name of the socket to which the connection should be established.
int namelen// [in] Length of name.
);
客户端是连接的发起者(initiate),它通过调用connect()函数主动(active)连接服务器。
TCP服务器接受客户连接请求
// The accept function permits an incoming connection attempt on a socket.
SOCKET accept(
SOCKET s,// [in] Descriptor identifying a socket that has been placed in a listening state with the listen function.
struct sockaddr FAR *addr,// [out] receives the address of the connecting entity, as known to the communications layer.
int FAR *addrlen// [out] the length of addr.
);
服务器进入listen状态后,循环调用accept()接受客户的连接。参数一为监听套接字;参数二为远端客户的地址信息;
该函数返回一个套接字句柄,负责后续与该远端客户的会话通信。
服务器调用socket的listen函数进入监听状态后,connect-accept完成的是TCP三次握手过程(three way or three message handshake):
在一个已绑定或已连接的套接字上获取连接名和对方地址信息
获取sockaddr
int getsockname (SOCKET s, struct sockaddr name, int namelen);
getsockname函数获取已绑定(可能是未调用bind的系统自动绑定)的套接口本地协议地址(sockaddr=IP:PORT)。
int getpeername (SOCKET s, struct sockaddr name, int namelen);
I/O通信
收发数据操作接口:send()/recv()。
关闭套接字(TCP连接)
在无连接的UDP中不存在关闭连接问题,我们recvfrom/sendto完毕即可调用closesocket()回收套接字内核资源。
对于面向连接的TCP通信,关闭一个连接需要四次挥手,以关闭双向信道。
// The shutdown function disables sends or receives on a socket.
int shutdown(
SOCKET s,// [in] Descriptor identifying a socket.
int how// [in] Flag that describes what types of operation will no longer be allowed.
);*

server.cpp

#include
#include

#pragma comment(lib,"WSOCK32.LIB")      //wsock32.dll
//报告错误
#define ReportWSAError(s) printf("Error #%d in %s.\n", WSAGetLastError(), #s)

int main(int argc, char * argv[])
{
    //初始化winsock
    WSADATA wsaData;
    WORD sockVersion = MAKEWORD(1, 0);  //合并两个字节
    if (WSAStartup(sockVersion, &wsaData))      //初始化不成功
    {
        ReportWSAError(WSAStartup());
        return 1;
    }

    //创建一个sock监听所有连接
    SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSocket == INVALID_SOCKET)     //数据类型不符
    {
        ReportWSAError(socket());
        WSACleanup(); // 清理套接字
        return 1;
    }

    // 绑定端口
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8888);
    sin.sin_addr.S_un.S_addr = INADDR_ANY; //任意本地地址

    if (bind(listenSocket, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        ReportWSAError(bind());
        WSACleanup(); // Terminates use of the wsock32.dll
        return 1;
    }
// Associates the listenSocket with a well-known address
    //等待连接
    if (listen(listenSocket, 2) == SOCKET_ERROR)
    {
        ReportWSAError(bind());
        WSACleanup(); // Terminates use of the wsock32.dll
        return 1;
    }

    //保存连接的客户机地址
    sockaddr_in remoteAddr;
    int nAddrLen = sizeof(remoteAddr);

    //为每个客户机创建一个套接字
    SOCKET serverSocket;
    char echo[] = "Message from the server! \r\n";

    while (TRUE)
    {
        serverSocket = accept(listenSocket, (sockaddr*)&remoteAddr, &nAddrLen);
        if (serverSocket == SOCKET_ERROR)
        {
            ReportWSAError(accept());
            continue;
        }

        printf("Accept a connection: %s \r\n", inet_ntoa(remoteAddr.sin_addr));
        send(serverSocket, echo, strlen(echo), 0);
        closesocket(serverSocket);
    }

    // 关闭sock
    if (closesocket(listenSocket) == SOCKET_ERROR)
    {
        ReportWSAError(closesocket());
        WSACleanup();
        return 1;
    }

    // 卸载库
    if (WSACleanup() == SOCKET_ERROR)
    {
        ReportWSAError(WSACleanup());
        return 1;
    }

    return 0; 
}

client.cpp

#include
#include
#pragma comment(lib,"WSOCK32.LIB")  //wsock32.dll

#define ReportWSAError(s) printf("Error #%d in %s./n", WSAGetLastError(), #s)

// class WSAInitializer is a part of the Socket class (on win32)
// as a static instance - so whenever an application uses a Socket,
// winsock is initialized
class WSAInitializer // Winsock Initializer
{
public:
    WSAInitializer()
    {
        if (WSAStartup(0x101, &m_wsadata))
        {
            exit(-1);
        }
    }

    ~WSAInitializer()
    {
        WSACleanup();
    }

private:
    WSADATA m_wsadata;
}WsaInit;

int main(int argc, char * argv[])
{
    //创建客户端winsock tcp连接
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) //sock创建失败
    {
        ReportWSAError(socket());       //返回错误
        return 1;
    }

    // Fill the sockaddr_in structure,
    // which describes the other side(server) of the connection to establish.
    // This demo connect to localhost.
    // Call function bind to associate a specified local address with the socket
    // before connect operation in the case of Multi-NIC.
    //太长了,不翻译了
    sockaddr_in servAddr;       //服务器地址
    servAddr.sin_family = AF_INET;  //使用的协议族
    servAddr.sin_port = htons(8888);    //转换为网络字节序
    servAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.120");     //服务器的网络地址

    //建立链接
    if (connect(clientSocket, (sockaddr*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
    {
        ReportWSAError(connect());      //链接未成功报告错误
        return 1;
    }

    //接收服务器数据
    char buff[256];
    int nRecv = recv(clientSocket, buff, 256, 0);
    if(nRecv > 0)
    {
        buff[nRecv] = '\0';     //填充一个字符串结束标志
        printf("Received data:%s\n", buff);
    }
    if (closesocket(clientSocket) == SOCKET_ERROR)
    {
        ReportWSAError(closesocket());
        return 1;
    }

    return 0;
}

一共测试了两次
第一次虚拟机使用的是共享地址
第二次虚拟机使用的是物理地址
C语言Socket实现网络通信_第1张图片
C语言Socket实现网络通信_第2张图片

你可能感兴趣的:(C语言Socket实现网络通信)