4-服务器程序编写

服务器程序编写

0.初始化网络库

WORD wVersionRequested;
WSADATA wsadata;
int err;
wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsadata);
if (err)
{
printf("WSAStartup errnum: %d\n", GetLastError());
return err;
}

if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2)
{
printf("LOBYTE errnum: %d\n", GetLastError());
WSACleanup();
return -1;
}

1.创建socket

1.1 socket

socket可以看成一个文件,向socket发送数据就是向其写入。也可以理解为一个网络连接,或者句柄。

SOCKET sockSrv;
1.2向操作系统申请socket

调用socket函数,向操作系统申请一块内存,并将唯一编号返回给socket

  • af:使用的协议簇

    • AF_UNIX:本机通信
    • AF_INET :TCP/UDP ipv4
    • AF_INET6:TCP/UDP ipv6
  • type:使用的套接字类型

    • SOCK_STREAM(TCP流)
    • SOCK_DGRAM(UDP数据报)
    • SOCK_RAW(原始套接字)
  • protocol:在确定协议簇和套接字的情况下,默认为0。在不确定的情况下,可以用于确定协议的种类。

sockSrv = socket(AF_INET, SOCK_STREAM, 0);

2.指定地址和端口

使用sockaddr_in,填充地址和端口

同时指定接收连接的IP地址可以是任意IP地址:INADDR_ANY(因为服务器可能有多个网卡,存在多个IP地址)(但客户端必须指定自己发送时的地址)

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);

3.绑定

将对应的socket绑定上对应的sockaddr

注意:端口可能在bind时被占用,导致失败,在此做好错误检测的准备

bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

4.监听

当socket函数创建一个套接字时,该套接字被假设为一个主动套接字。在listen函数中,传入一个未连接的socket,将其转换为被动套接字,指示内核应接收指向该socket的连接请求。

第二个参数指定了内核为该socket排队的最大连接个数。

listen(sockSrv, 5);

5.接收连接请求

当listen后,服务端socket里还有连接,则将队列头的连接通过accept来接收,并保存该连接对应的IP和端口信息,最后返回一个假设的客户端socket

注:accept函数是阻塞式的,在服务端连接队列为空时会一直等待(一定要先初始化网络库才行)

SOCKADDR_IN addrCli;			//创建结构体,用于保存新的连接IP和端口
int len = sizeof(SOCKADDR);

while (1)
{
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
//创建socket,从sockSrv中获取连接上的socket信息,并将对应的IP和端口置入addrCli
char sendBuf[100] = {0};
sprintf_s(sendBuf,100,"Welcome %s to China!",inet_ntoa(addrCli.sin_addr));

}

6.发送信息

使用send函数,向sockConn发送sendBuf里的信息,默认阻塞

注意,返回值<0出错,==0超时对方关闭连接,>0为发送数据大小

char sendBuf[100] = { 0 };
sprintf_s(sendBuf, 100, "Welcome %s to China!", inet_ntoa(addrCli.sin_addr));//写入sendBuf信息

int iLen = send(sockConn, sendBuf, strlen(sendBuf), 0);

7.接收信息

使用recv函数,向sockConn接收信息到sendBuf里,默认阻塞

返回值<0出错,==0超时对方关闭连接,>0为接收数据大小

char recvBuf[100] = { 0 };
iLen = recv(sockConn, recvBuf, 100, 0);
printf("RecvBuf : %s", recvBuf);

8.关闭连接

closesocket(sockConn);

9.补充

对于每一种网络操作函数,都会返回一个数,如果这个数==SOCKET_ERROR,则出错,调用GetLastError()函数来返回对应的错误码。

由此可以利用宏定义来简化错误显示

#define errprint(errMsg) printf("[line:%d]%s failed code : %d",__LINE__,errMsg,WSAGetLastError())
案例
if (SOCKET_ERROR == send(fd, buf, strlen(buf), 0))
{
errprint("send");
}

完整代码

#include<WinSock2.h>
#include<stdio.h>
#include<stdlib.h>

#pragma comment(lib,"ws2_32.lib")
int main()
{
WORD wVersionRequested;
WSADATA wsadata;
int err;
wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsadata);
if (err)
{
printf("WSAStartup errnum: %d\n", GetLastError());
return err;
}

if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2)
{
printf("LOBYTE errnum: %d\n", GetLastError());
WSACleanup();
return -1;
}

SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sockSrv)
{
printf("%d", GetLastError());
return -1;
}

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(9999);

if (SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
{
printf("%d", GetLastError());
return -1;
}


if (SOCKET_ERROR == listen(sockSrv, 5))
{
printf("%d", GetLastError());
return -1;
}

SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);

while (1)
{
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
if (sockConn == SOCKET_ERROR)
{
printf("%d", GetLastError());
return -1;
}

//创建socket,从sockSrv中获取连接上的socket信息,并将对应的IP和端口置入addrCli
char sendBuf[100] = { 0 };
sprintf_s(sendBuf, 100, "Welcome %s to China!", inet_ntoa(addrCli.sin_addr));

int iLen = send(sockConn, sendBuf, strlen(sendBuf), 0);

char recvBuf[100] = { 0 };
iLen = recv(sockConn, recvBuf, 100, 0);
printf("RecvBuf : %s", recvBuf);
closesocket(sockConn);

}

closesocket(sockSrv);
}