⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 server_notes.c

📁 RIP协议所采用的UDP通信模块,该程序是UDP通信的服务器端程序
💻 C
字号:
/****************************************************
*Windows环境下使用VC编写的TCP通信服务器端程序
*在编译这个程序时,需要在工程设置项目--〉连接项目中添加如下两项:
*        Ws2_32.lib和Winmm.lib
*否则编译后链接生成可执行文件时会出错!
*服务器程序在单机内的测试运行方法:
* server [监听端口:缺省为9999]
* 
*程序中的函数列表:
*int InitSockets(void): 插口(套接字)初始化
*void ServeAClient(LPVOID lpv):为客户提供服务的函数
*int ServerLoop(SOCKET sd_listen, int isMultiTasking):
*         服务器循环函数,服务期间总是在这个函数中循环
*********************************************************/

#include <stdio.h>
#include <winsock.h>
#include <stdlib.h>
/*
由于Winsock目前有两个版本:2.2和1.1,所以我们首先必须判断系统所支持的Winsock版本,这就要靠WSAStartup函数了。
另外还有一个WSACleanup函数,这两个函数是Winsock编程必须调用的,其中WSAStartup函数的功能是初始化Winsock DLL,因为在Windows下,Socket是以DLL的形式实现的。1.1版本的DLL为Winsock.dll,而2.2版本的DLL则为Wsock32.dll,其中在2.2版本的系统中,对Winsock1.1函数的调用会由Wsock32.dll自动映射到Winsock.dll。WSAStartup函数的功能就是初始化DLL,其函数原型为: int WSAStartup (WORD wVersionRequested,LPWSADATA lpWSAData); 其第一个参数为你所想需要的Winsock版本!低字节为主版本,高字节为副版本!由于目前Winsock有两个版本:1.1和2.2,因此该参数可以是0x101或0x202;第2个参数是一个WSADATA结构,用于接收函数的返回信息!WSAStartup函数调用成功会返回0,否则返回非0值! 
由于Win 95,WinNT4自带的Winsock是1.1版本的,所以如果你的程序是基于Winsock2.2的,那很可能无法在上面运行。因此,如果你希望你写的程序被所有Windows平台支持的话,最好将其声明成1.1版的,不过这样将无法使用很多Winsock2.2才有的特性!至于WSACleanup的用法很简单,用“WSACleanup();”就行了!另外,在DLL内部维持着一个计数器,只有第一次调用WSAStartup才真正装载DLL,以后的调用只是简单的增加计数器,而WSACleanup函数的功能则刚好相反,每调用一次使计数器减1,当计数器减到0时,DLL就从内存中被卸载!因此,你调用了多少次WSAStartup,就应相应的调用多少次的WSACleanup!
*/

int InitSockets(void)
{
    WSADATA wsaData;
    WORD wVersionRequested;
    int err;
    /* Ask for Winsock 1.1 functionality */
    wVersionRequested = MAKEWORD( 1, 1 );
    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
      printf("Error %d: Winsock not available\n", err);
      return 1;
      }
    return 0;
}

#define WSA_ERROR(x)  { printf("Error %d: %s\n", \
    WSAGetLastError(), x); return 1; }

int ServerLoop(SOCKET sd_listen, int isMultiTasking);

main(int argc, char **argv)
{
    SOCKET sd_listen;
    int err;
    u_short iPort;
    struct sockaddr_in addr_srv;
    struct hostent *ptrHost;
    
    iPort = (argc >= 2) ? atoi(argv[1]) : 9999;//从命令行中提取服务器监控的端口号,如果命令行没有带数字格式的端口号,则定为9999
    
    InitSockets();//初始化套接字
/*
创建套接字有两个函数,socket和WSASocket,前者是标准的Socket函数,而后者是微软对Socket的扩展函数。socket函数有3个参数,第一个是指定通信发生的区域,在UNIX下有AF_UNIX、AF_INET、AF_NS等,而在Winsock1.1下只支持AF_INET,到了2.2则添了AF_IRDA(红外线通信)、AF_ATM(异步网络通信)、AF_NS、AF_IPX等;第2个参数是套接字的类型,在AF_INET地址族下,有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW三种套接字类型。SOCK_STREAM也就是通常所说的TCP,而SOCK_DGRAM则是通常所说的UDP,而SOCK_RAW则是用于提供一些较低级的控制的;第3个参数依赖于第2个参数,用于指定套接字所用的特定协议,设为0表示使用默认的协议。socket函数调用成功返回一个套接字描述符,错误则返回SOCKET_ERROR。 
*/
    sd_listen = socket(PF_INET, SOCK_STREAM, 0);
    if (sd_listen == INVALID_SOCKET) {
        printf("Error: out of socket resources\n");
        return 1;
        }
/*
接下来要为服务器端定义的这个监听的socket指定一个地址及端口(Port),这样客户端才知道要连接哪一个地址的哪个端口,为此我们首先设置数据结构struct sockaddr中的参数,随之调用bind()函数将socket邦定在指定的端口和地址上,该函数调用成功返回0,否则返回SOCKET_ERROR。sockaddr_in结构的sin_addr.s_addr成员被设置为INADDR_ANY,意味着由操作系统给socket自动指定一个最有效的IP地址。
*/
    addr_srv.sin_family = PF_INET;
    addr_srv.sin_addr.s_addr = htonl(INADDR_ANY);
    addr_srv.sin_port = htons(iPort);

    err = bind(sd_listen, (const struct sockaddr *) &addr_srv,
               sizeof(addr_srv));
    if (err == INVALID_SOCKET)
        WSA_ERROR("Error: unable to bind socket\n")
/*
当服务器端的Socket对象绑定完成之后,服务器端必须通知操作系统为这个socket建立一个监听队列来接收客户端的连接请求。listen()函数使服务器端的Socket 进入监听状态,并设定可以建立的最大连接数。该函数调用成功返回0,否则返回SOCKET_ERROR。
*/
    err = listen(sd_listen, SOMAXCONN);
    if (err == INVALID_SOCKET)
        WSA_ERROR("Error: listen failed\n")

//程序进入服务循环函数
    ServerLoop(sd_listen, 1);
    printf("Server is down\n");
    WSACleanup(); 
    return 0;
}



/*
如果客户端发来HELLO SERVER,
那么服务器端将回送HELLO CLIENT,
不符合的输入,服务器端将没有响应
*/
void ServeAClient(LPVOID lpv)
{
    SOCKET sd_accept = (SOCKET) lpv;
    const char *msg = "HELLO CLIENT";
    char response[4096];

    memset(response, 0, sizeof(response));
    recv(sd_accept, response, sizeof(response), 0);
    if (strcmp(response, "HELLO SERVER")) {
        printf("Application:  client not using expected "
            "protocol %s\n", response);
      }
    else
        send (sd_accept, msg, strlen(msg)+1, 0);
    closesocket(sd_accept);
}

#define   MAX_SERVED         3
//服务器端的循环函数,服务期间总是在这个函数中运行
int ServerLoop(SOCKET sd_listen, int isMultiTasking)
{
    SOCKET sd_accept;
    struct sockaddr_in addr_client;
    int err, nSize;
    int numServed = 0;
    HANDLE handles[MAX_SERVED];
    int myID;
  //主循环体,服务期间总是在这个结构内循环
    while (numServed < MAX_SERVED) {

      nSize = sizeof(addr_client);
    //在套接字sd_listen上接收到客户端的连接请求后,建立一个新的套接字sd_accept,
    //使用套接字sd_accept与客户端进行通信
      sd_accept = accept(sd_listen, (struct sockaddr *)
                         &addr_client, &nSize);
      if (sd_accept == INVALID_SOCKET)
          WSA_ERROR("Error: accept failed\n")

      printf("Accepted connection from client at %s\n",
             inet_ntoa(addr_client.sin_addr));
    //如果服务器端允许工作在并发多任务模式(isMultiTasking为程序员设定的标志)
      if (isMultiTasking) {
#ifdef _WIN32 //适应不同版本的编译环境,较新的系统都定义了_WIN32
    // 建立一个新的线程,线程程序代码为ServeAClient,传递的参数为通信套接字sd_accept
          handles[numServed] = CreateThread(NULL, 1000,
                      (LPTHREAD_START_ROUTINE)ServeAClient,
                      (LPVOID) sd_accept, 0, &myID);
#else
          myID = fork();
          if (myID == 0) { /* I am child process */
             ServeAClient ((LPVOID) sd_accept);
             exit(0);
             }
          handles[numServed] = myID;
#endif
          }
      else
          ServeAClient((LPVOID) sd_accept);
      numServed++;
      }

    if (isMultiTasking) {
#ifdef _WIN32
        //等待线程组handles中的线程结束,并显示运行状态
        err = WaitForMultipleObjects(MAX_SERVED, handles,
                                     TRUE, INFINITE);
        printf("Last thread to finish was thread #%d\n", err);
#endif
        }
    return 0;
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -