Linux下各类TCP网络服务器的实现源代码

Linux下TCP/IP服务器实现源码解析(含源码) 对于菜鸟接触linux,网络是由浅入深的步步提高深化的源码解析笔记! 其中不乏老鸟也需要深入的技术! (内附赠送linux内核分析图和linux文件解析表) 希望大家互相交流 共同进步!! 拜交好友! 立即下载

Linux下各类TCP网络服务器的实现源代码

关键词 : Linux     C语言     tcp     网络     编程                                           

Linux下各类TCP网络服务器的实现源代码

大家都知道各类网络服务器程序的编写步骤,并且都知道网络服务器就两大类:循环服务和并发服务。这里附上源代码来个小结吧。

首先,循环网络服务器编程实现的步骤是这样的:

                              建立socket(这里用到socket()函数及函数setsockopt())
                                                            |
                                                            |
                                                           \|/
                              把socket和IP地址及端口绑定(这里用到bind函数)
                                                            |
                                                            |
                                                           \|/
                              开始监听(这里用到listen()函数)
                                                            |
                                                            |
                                                           /\
                                                       /        \
                                               \     /           \
                    -----------------------  | 有连接|
                    |                           /   \             /
                    |                                 \         /
                    |                                     \ /
                    |                                       | 
                    |          接受新的连接(这里用到accept()函数)
                    |                                       |  /___________________________________________________
                    |                                       |  \                                                                                                                            |
                    |                                     \|/                                                                                                                              |
                    |         从连接里读取数据(这里用到recv()系统函数,当然也可以是read()函数)                          |
                    |                                        |                                                                                                                                |
                    |                                       |                                                                                                                                |
                    |                                     \|/                                                                                                                             |
                    |          返回信息给连接(这里用到send()系统函数,当然也可以是write()函        数)                    |
                    |                                       |                                                                                                                                |
                    |                                       |                                                                                                                                |
                    |                                      /\                                                                                                                             |
                    |                                  /        \                                                                                                                         |
                    |                               /               \                                                                                                                      |
                    |                              | 还有数据 |-Y-------------------------------------------------------
                    |                              \               /
                    |                                 \         /
                    |                                     \ /
                    |________________|

这种服务器模型是典型循环服务,如果不加上多进程/线程技术,此种服务吞吐量有限,大家都可以看到,如果前一个连接服务数据没有收发完毕后面的连接没办法处理。所以一般有多进程技术,对一个新连接启用一个新进程去处理,而监听socket继续监听。

/************关于本文档********************************************
*filename: Linux下各类TCP网络服务器的实现源代码
*purpose: 记录Linux下各类tcp服务程序源代码
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-04 22:00:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*********************************************************************/

一个循环TCP服务源代码(因为用fork进行多进程服务了,所以这种服务现实中也有用)如下:

/*----------------------源代码开始--------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
/*********************************************************************
*filename: cycletcpserver.c
*purpose: 循环tcp服务端程序
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-04 22:00:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*********************************************************************/
int main(int argc, char ** argv)
{
    int sockfd,new_fd; /* 监听socket: sock_fd,数据传输socket: new_fd */
    struct sockaddr_in my_addr; /* 本机地址信息 */
    struct sockaddr_in their_addr; /* 客户地址信息 */
    unsigned int sin_size, myport, lisnum;

    if(argv[1])  myport = atoi(argv[1]);
    else myport = 7838;

    if(argv[2])  lisnum = atoi(argv[2]);
    else lisnum = 2;

    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket"); 
        exit(1); 
    }
    my_addr.sin_family=PF_INET;
    my_addr.sin_port=htons(myport);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero), 0);
    if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(sockfd, lisnum) == -1) {
        perror("listen");
        exit(1);
    }
    while(1) {
        sin_size = sizeof(struct sockaddr_in);
        if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
            perror("accept");
            continue;
        }
        printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
        if (!fork()) { /* 子进程代码段 */
            if (send(new_fd, "Hello, world!\n", 14, 0) == -1) {
                perror("send");
                close(new_fd);
                exit(0);
            }
        }
        close(new_fd); /*父进程不再需要该socket*/
        waitpid(-1,NULL,WNOHANG);/*等待子进程结束,清除子进程所占用资源*/
    }
}
/*----------------------源代码结束--------------------------------------------*/

一个测试客户端代码如下:

/*----------------------源代码开始--------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MAXDATASIZE 100 /*每次最大数据传输量 */
/*********************************************************************
*filename: cycletcpclient.c
*purpose: 循环tcp客户端程序
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-04 22:20:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*********************************************************************/

int main(int argc, char *argv[])
{
    int sockfd, numbytes;
    char buf[MAXDATASIZE];
    struct hostent *he;
    struct sockaddr_in their_addr;
    unsigned int myport;

    if(argv[2]) myport = atoi(argv[2]);
    else myport = 7838;

    if (argc != 3) {
        fprintf(stderr,"usage: %s hostname port\n", argv[0]); 
        exit(1);
    }
    if((he=gethostbyname(argv[1]))==NULL) {
        herror("gethostbyname");
        exit(1);
    }
    if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
    their_addr.sin_family=PF_INET;
    their_addr.sin_port=htons(myport);
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    bzero(&(their_addr.sin_zero),0);
    if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
        perror("connect");
        exit(1);
    }
    if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
        perror("recv");
        exit(1);
    }
    buf[numbytes] = 0;
    printf("Received: %s\n",buf);
    close(sockfd);
    return 0;
}
/*----------------------源代码结束--------------------------------------------*/

用gcc cycletcpserver.c -o tcpserver和gcc cycletcpclient.c -o tcpclient分别编译上述代码后运行情况如下:
服务端运行显示:
administrator@ubuzlf:/data/example/c$ ./tcpserver
server: got connection from 127.0.0.1
server: got connection from 127.0.0.1
server: got connection from 127.0.0.1
客户端运行显示:

administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
Received: Hello, world!

administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
Received: Hello, world!

administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
Received: Hello, world!

不得不说的一个概念性问题:阻塞与非阻塞
在阻塞服务中,当服务器运行到accept语句而没有客户连接服务请求到来,那么会发生什么情况?这时服务器就会停止在accept语句上等待连接服务请求的到来;同样,当程序运行到接收数据语句recv时,如果没有数据可以读取,则程序同样会停止在接收语句上。这种情况称为阻塞(blocking)。
但如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接;否则就继续做其他事情,则可以通过将 socket设置为非阻塞方式来实现:非阻塞socket在没有客户在等待时就使accept调用立即返回 。
通过设置socket为非阻塞方式,可以实现“轮询”若干socket。当企图从一个没有数据等待处理的非阻塞socket读入数据时,函数将立即返回,并且返回值置为-1,并且errno置为EWOULDBLOCK。但是这种“轮询”会使CPU处于忙等待方式,从而降低性能。考虑到这种情况,假设你希望服务器监听连接服务请求的同时从已经建立的连接读取数据,你也许会想到用一个accept语句和多个recv()语句,但是由于accept及recv都是会阻塞的,所以这个想法显然不会成功。
调用非阻塞的socket会大大地浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。

其次,并发服务器,在上述cycletcpserver.c中,由于使用了fork技术也可以称之为并发服务器,但这种服务器并不是真正意义上的IO多路复用的并发服务器,并且由于没有处理阻塞问题,实际应用有各种各样的问题。

一个典型IO多路复用的单进程并发服务器流程如下:
/*IO多路复用并发服务流程图*/
简单IO复用流程图

下面是一个演示IO多路复用的源程序,是一个端口转发程序,但它的用处相当大,实际应用中的各类代理软件或端口映射软件都是基于这样的代码的,比如Windows下的WinGate、WinProxy等都是在此基础上实现的。源代码如下:

/*----------------------源代码开始--------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

static int forward_port;

#undef max
#define max(x,y) ((x) > (y) ? (x) : (y))

/*************************关于本文档************************************
*filename: tcpforwardport.c
*purpose: 演示了select的用法,这是一个极好的代理软件核心,专门作端口映射用
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-05 19:00:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*********************************************************************/

static int listen_socket (int listen_port) {
    struct sockaddr_in a;
    int s;
    int yes;
    if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
        perror ("socket");
        return -1;
    }
    yes = 1;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) <
0) {
        perror ("setsockopt");
        close (s);
        return -1;
    }
    memset (&a, 0, sizeof (a));
    a.sin_port = htons (listen_port);
    a.sin_family = AF_INET;
    if (bind(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
        perror ("bind");
        close (s);
        return -1;
    }
    printf ("accepting connections on port %d\n", (int) listen_port);
    listen (s, 10);
    return s;
}

static int connect_socket (int connect_port, char *address) {
    struct sockaddr_in a;
    int s;
    if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
        perror ("socket");
        close (s);
        return -1;
    }

    memset (&a, 0, sizeof (a));
    a.sin_port = htons (connect_port);
    a.sin_family = AF_INET;

    if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
        perror ("bad IP address format");
        close (s);
        return -1;
    }

    if (connect(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
        perror ("connect()");
        shutdown (s, SHUT_RDWR);
        close (s);
        return -1;
    }
    return s;
}

#define SHUT_FD1 { \
    if (fd1 >= 0) {   \
        shutdown (fd1, SHUT_RDWR);  \
        close (fd1);  \
        fd1 = -1;     \
    }   \
}

#define SHUT_FD2 { \
    if (fd2 >= 0) {   \
        shutdown (fd2, SHUT_RDWR);  \
        close (fd2);  \
        fd2 = -1;     \
    }   \
}

#define BUF_SIZE 1024

int main (int argc, char **argv) {
    int h;
    int fd1 = -1, fd2 = -1;
    char buf1[BUF_SIZE], buf2[BUF_SIZE];
    int buf1_avail, buf1_written;
    int buf2_avail, buf2_written;

    if (argc != 4) {
        fprintf (stderr, "Usage\n\tfwd   \n");
        exit (1);
    }

    signal (SIGPIPE, SIG_IGN);

    forward_port = atoi (argv[2]);

    /*建立监听socket*/
    h = listen_socket (atoi (argv[1]));
    if (h < 0) exit (1);

    for (;;) {
        int r, nfds = 0;
        fd_set rd, wr, er;
        FD_ZERO (&rd);
        FD_ZERO (&wr);
        FD_ZERO (&er);
        FD_SET (h, &rd);

        /*把监听socket和可读socket三个一起放入select的可读句柄列表里*/
        nfds = max (nfds, h);
        if (fd1 > 0 && buf1_avail < BUF_SIZE) {
            FD_SET (fd1, &rd);
            nfds = max (nfds, fd1);
        }
        if (fd2 > 0 && buf2_avail < BUF_SIZE) {
            FD_SET (fd2, &rd);
            nfds = max (nfds, fd2);
        }

        /*把可写socket两个一起放入select的可写句柄列表里*/
        if (fd1 > 0 && buf2_avail - buf2_written > 0) {
            FD_SET (fd1, &wr);
            nfds = max (nfds, fd1);
        }
        if (fd2 > 0 && buf1_avail - buf1_written > 0) {
            FD_SET (fd2, &wr);
            nfds = max (nfds, fd2);
        }

        /*把有异常数据的socket两个一起放入select的异常句柄列表里*/
        if (fd1 > 0) {
            FD_SET (fd1, &er);
            nfds = max (nfds, fd1);
        }
        if (fd2 > 0) {
            FD_SET (fd2, &er);
            nfds = max (nfds, fd2);
        }

        /*开始select*/
        r = select (nfds + 1, &rd, &wr, &er, NULL);

        if (r == -1 && errno == EINTR) continue;
        if (r < 0) {
            perror ("select()");
            exit (1);
        }

        /*处理新连接*/
        if (FD_ISSET (h, &rd)) {
            unsigned int l;
            struct sockaddr_in client_address;
            memset (&client_address, 0, l = sizeof (client_address));
            r = accept (h, (struct sockaddr *)&client_address, &l);
            if (r < 0) {
                perror ("accept()");
            } else {
                /*关闭原有连接,把新连接作为fd1,同时连接新的目标fd2*/
                SHUT_FD1;
                SHUT_FD2;
                buf1_avail = buf1_written = 0;
                buf2_avail = buf2_written = 0;
                fd1 = r;
                fd2 = connect_socket (forward_port, argv[3]);
                if (fd2 < 0) {
                    SHUT_FD1;
                } else
                    printf ("connect from %s\n", inet_ntoa(client_address.sin_addr));
            }
        }

        /* NB: read oob data before normal reads */
        if (fd1 > 0)
        if (FD_ISSET (fd1, &er)) {
            char c;
            errno = 0;
            r = recv (fd1, &c, 1, MSG_OOB);
            if (r < 1) {
                SHUT_FD1;
            } else
                send (fd2, &c, 1, MSG_OOB);
        }

        if (fd2 > 0)
        if (FD_ISSET (fd2, &er)) {
            char c;
            errno = 0;
            r = recv (fd2, &c, 1, MSG_OOB);
            if (r < 1) {
                SHUT_FD1;
            } else
                send (fd1, &c, 1, MSG_OOB);
        }

        /* NB: read data from fd1 */
        if (fd1 > 0)
        if (FD_ISSET (fd1, &rd)) {
            r = read (fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail);
            if (r < 1) {
                SHUT_FD1;
            } else
                buf1_avail += r;
        }

        /* NB: read data from fd2 */
        if (fd2 > 0)
        if (FD_ISSET (fd2, &rd)) {
            r = read (fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail);
            if (r < 1) {
                SHUT_FD2;
            } else
                buf2_avail += r;
        }

        /* NB: write data to fd1 */
        if (fd1 > 0)
        if (FD_ISSET (fd1, &wr)) {
            r = write (fd1, buf2 + buf2_written, buf2_avail - buf2_written);
            if (r < 1) {
                SHUT_FD1;
            } else
                buf2_written += r;
        }

        /* NB: write data to fd1 */
        if (fd2 > 0)
        if (FD_ISSET (fd2, &wr)) {
            r = write (fd2, buf1 + buf1_written, buf1_avail - buf1_written);
            if (r < 1) {
                SHUT_FD2;
            } else
                buf1_written += r;
        }

        /* check if write data has caught read data */
        if (buf1_written == buf1_avail) buf1_written = buf1_avail = 0;
        if (buf2_written == buf2_avail) buf2_written = buf2_avail = 0;

        /* one side has closed the connection, keep writing to the other side until empty */
        if (fd1 < 0 && buf1_avail - buf1_written == 0) {
            SHUT_FD2;
        }
        if (fd2 < 0 && buf2_avail - buf2_written == 0) {
            SHUT_FD1;
        }
    }
    return 0;
}
/*----------------------源代码结束--------------------------------------------*/

用gcc tcpforwardport.c -o MyProxy编译此程序后运行效果如下:
./MyProxy 8000 80 172.16.100.218
accepting connections on port 8000
connect from 127.0.0.1

当有用户访问本机的8000端口时,MyProxy程序将把此请求转发到172.16.100.218主机的80端口,即实现了一个http代理。

关于select函数:
其函数原型为:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
此函数的功能是由内核检测在timeout时间内,是否有readfds,writefds,exceptfds三个句柄集(file descriptors)里的某个句柄(file descriptor)的状态符合寻求,即readfds句柄集里有句柄可读或writefds句柄集里有可写或exceptfds句柄集里有例外发生,任何一个有变化函数就立即返回,返回值为timeout发生状态变化的句柄个数。
n是所有readfds,writefds,exceptfds三个句柄集(file descriptors)里编号最大值加1。比如:要检测两个socket句柄fd1和fd2在timeout时间内是否分别可读和可写就可以这样:
先把两个句柄集(file descriptors)清零:
        FD_ZERO (&readfds);
        FD_ZERO (&writefds);
然后把fd1加入读检测集:
        FD_SET (fd1, &readfds);
然后把fd2加入写检测集:
        FD_SET (fd2, &writefds);
再给timeout设置值,timeout是这样的一个结构:
              struct timeval {
                  long    tv_sec;         /* seconds */
                  long    tv_usec;        /* microseconds */
              };
你可以这样赋值:
        timeout.tv_sec=1;
        timeout.tv_uec=0;
表示检测在1秒钟内是否有句柄状态发生变化。
如果有句柄发生变化,就可以用FD_ISSET检测各个句柄,比如:
                FD_ISSET (fd1, &readfds);//检测是否fd1变成可读的了
                FD_ISSET (fd2, &writefds);//检测是否fd2变成可写的了
示意程序代码如下:

/*----------------------示意代码开始--------------------------------------------*/
    fd1 = socket();//创建一个socket
    fd2 = socket();//创建一个socket
    while(1)  {
        FD_ZERO (&readfds);
        FD_ZERO (&writefds);
        FD_SET (fd1, &readfds);
        FD_SET (fd2, &writefds);
        timeout.tv_sec=1;
        timeout.tv_uec=0;
        ret = select(fd1>fd2?(fd1+1):(fd2+1), &readfds, &writefds, NULL, &timeout);
        if(ret < 0) {printf("系统错误,select出错,错误代码:%d, 错误信息:%s", errno, strerror(errno));}
        else if(ret == 0) {printf("select超时返回,没有任何句柄状态发生变化!");}
        //有句柄状态发生了变化
        if(FD_ISSET(fd1, &readfds)) {
            fd1有数据可读;
            fd1里的数据被读出来;
        }
        if(FD_ISSET(fd2, &writefds)) {
            fd2可写;
            fd2里发送数据给对方;
        }
    }
/*----------------------示意代码结束--------------------------------------------*/

经常用到的几个自定义函数:
1、开启监听的函数

/*----------------------源代码代码开始--------------------------------------------*/
int
OpenSCPServer(int port, int total, int sendbuflen, int recvbuflen, int blockORnot, int reuseORnot)    {
/*************************关于本函数************************************
*function_name: OpenSCPServer
*参数说明:port整数型监听端口号,total整数型监听个数,sendbuflen整数型发送缓冲区大小
*          recvbuflen整数型接收缓冲区大小,blockORnot整数型是否阻塞,reuseORnot整数型是否端口重用
*purpose: 用来建立一个tcp服务端socket
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-05 20:00:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含GetCurrentTime.h头文件
*********************************************************************/
    int    sockfd = 0, ret = 0, opt = 0, flags=1;
    struct sockaddr_in    laddr;

    ret = sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer socket() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -1;
    }

    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseORnot, sizeof(int));
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() reuse error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -2;
    }

    ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvbuflen, sizeof(int));
    if ( ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() recvbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -3;
    }

    ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuflen, sizeof(int));
    if (ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() sendbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -4;
    }

    ioctl(sockfd,FIONBIO,&blockORnot);/*block or not*/

    laddr.sin_family = PF_INET;
    laddr.sin_port = htons(port);
    laddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(laddr.sin_zero), 8);

    ret = bind(sockfd, (struct sockaddr *)&laddr, sizeof(struct sockaddr));
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer bind() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        close(sockfd);
        return -5;
    }
    ret = listen(sockfd, total);
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer listen() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        close(sockfd);
        return -6;
    }
    sprintf(errorMessage, "OpenTCPServer opened on port.%d(%d) OK, socket(%d), buf(%d:%d)! %s", port, total, sockfd, sendbuflen, recvbuflen, GetCurrentTime(0, 0));
    return sockfd;
}
/*----------------------源代码代码结束--------------------------------------------*/

2、连接服务器的函数

/*----------------------源代码代码开始--------------------------------------------*/
int
ConnectSCPServer(char * serverip, int serverport, int blockORnot)    {
/*************************关于本函数************************************
*function_name: ConnectSCPServer
*参数说明:serverip服务器IP地址或主机名,serverport服务器端口,blockORnot整数型是否阻塞
*purpose: 用来建立一个tcp客户端socket
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-05 20:40:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
*********************************************************************/
    int    serversock = 0, ret = 0;
    unsigned long    addr;
    struct sockaddr_in    sin;
    struct hostent *he;

    if((he=gethostbyname(serverip))== 0) {
        sprintf(errorMessage, "ConnectSCPServer IP address '%s' error! return:-1 %s", serverip, GetCurrentTime(0, 0));
        return -1;
    }

    serversock = socket(PF_INET, SOCK_STREAM, 0);
    if(serversock == -1)    {
        sprintf(errorMessage, "ConnectSCPServer socket() error! return:-2, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
        return -2;
    }

    ioctl(serversock, FIONBIO, &blockORnot);  //block or not

    memset((char*)&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = PF_INET;
    sin.sin_port = htons(serverport);
    sin.sin_addr = *((struct in_addr *)he->h_addr);

    ret = connect(serversock, (struct sockaddr *)&sin, sizeof(sin));

    if(ret == -1)    {
        sprintf(errorMessage, "ConnectSCPServer connect() error! return:-3, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
        close(serversock);
        return -3;
    }

    return serversock;
}
/*----------------------源代码代码结束--------------------------------------------*/

3、发送数据函数Send

/*----------------------源代码代码开始--------------------------------------------*/
int
Send(int sock, char * buf, size_t size, int flag, int timeout)    {
/*************************关于本函数************************************
*function_name: Send
*参数说明:sock整数型socket,buf待发送的内容,size要发送的大小,flag发送选项,timeout超时时间值
*purpose: 用来通过一个socket在指定时间内发送数据
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-05 20:58:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
*********************************************************************/
    int i = 0, ret = 0, intretry = 0;

    struct timeval tival;
    fd_set writefds;
    int maxfds = 0;

    tival.tv_sec = timeout;
    tival.tv_usec = 0;

    FD_ZERO(&writefds);

    if(sock > 0) {
        FD_SET(sock, &writefds);
        maxfds=((sock > maxfds)?sock:maxfds);
    }
    else    {
        sprintf(errorMessage, "Send socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
        return -2;
    }

    ret = select(maxfds + 1, NULL, &writefds, NULL, &tival);
    if(ret <= 0) {
        if(ret < 0)    sprintf(errorMessage, "Send socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
        else sprintf(errorMessage, "Send socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
        close(sock);
        return -3;
    }
    if(!(FD_ISSET(sock, &writefds)))    {
        sprintf(errorMessage, "Send socket:%d not in writefds! %s", sock, GetCurrentTime(0, 0));
        close(sock);
        return -4;
    }

    while(i < size)    {
        ret = send(sock, buf + i, size - i, flag);
        if(ret <= 0)    {
            sprintf(errorMessage, "Send socket:%d send() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));

            if (EINTR == errno)
              if(intretry < 10)  {intretry++;continue;}
              else sprintf(errorMessage, "Send socket:%d send() error!EINTR 10 times! %s", sock, GetCurrentTime(0, 0));

            close(sock);
            return -1;
        }
        else i += ret;
    }
    sprintf(errorMessage, "Send socket:%d send() OK! %d/%d bytes sent! %s", sock, i, size, GetCurrentTime(0, 0));
    return i;
}
/*----------------------源代码代码结束--------------------------------------------*/

4、接收数据函数Recv

/*----------------------源代码代码开始--------------------------------------------*/
int
Recv(int sock, char * buf, size_t size, int flag, int timeout)    {
/*************************关于本函数************************************
*function_name: Recv
*参数说明:sock整数型socket,buf接收数据的缓冲区,size要接收数据的大小,flag接收选项,timeout超时时间值
*purpose: 用来从一个socket在指定时间内读取数据
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2006-07-05 21:10:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Paul Sheer 感谢Paul Sheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量char errorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
*********************************************************************/
    int i = 0, ret = 0, intretry = 0;

    struct timeval tival;
    fd_set readfds;
    int maxfds = 0;

    tival.tv_sec = timeout;
    tival.tv_usec = 0;

    FD_ZERO(&readfds);

    if(sock > 0) {
        FD_SET(sock, &readfds);
        maxfds=((sock > maxfds)?sock:maxfds);
    }
    else    {
        sprintf(errorMessage, "Recv socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
        return -2;
    }

    ret = select(maxfds + 1, &readfds, NULL, NULL, &tival);
    if(ret <= 0) {
        if(ret < 0)    sprintf(errorMessage, "Recv socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
        else sprintf(errorMessage, "Recv socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
        close(sock);
        return -3;
    }
    if(!(FD_ISSET(sock, &readfds)))    {
        sprintf(errorMessage, "Recv socket:%d not in readfds! %s", sock, GetCurrentTime(0, 0));
        close(sock);
        return -4;
    }
    while(i < size)    {
        ret = recv(sock, buf + i, size - i, flag);
        if(ret <= 0){
            sprintf(errorMessage, "Recv socket:%d recv() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
            if(errno == EINTR)    
                if(intretry < 10)  {intretry++;continue;}
                else sprintf(errorMessage, "Recv socket:%d recv() error! EINTR 10 times! %s", sock, GetCurrentTime(0, 0));
            close(sock);
            return -1;
        }
        else i += ret;
    }
    sprintf(errorMessage, "Recv socket:%d recv() OK! %d/%d bytes received! %s", sock, i, size, GetCurrentTime(0, 0));
    return i;
}

最后需要说明的是:我这里讲到的源程序并不能实际地作为一个产品程序来用,实际情况下可能会有其它许多工作要做,比如可能要建立共享队列来存放socket里读到的消息,也可能把发送消息先进行排队然后再调用Send函数。还有,如果不是全数字,在发送前一定要htonl转换为网络字节序,同理接收到后一定要先ntohl由网络字节序转换为主机字节序,否则对方发送过来的0x00000001在你这里可能是0x00010000,因为高低位顺序不同。
Linux下TCP网络服务器实现源代码 Linux源代码,Linux下TCP网络服务器实现源代码 立即下载

相关推荐

linux平台TCP协议实现文件传输源码

一、功能说明:LINUX平台TCP编程实现client端像server端传输文件,支持多client。 二、编译运行命令说明 1、编译   client:gcc -o client client.c   server:gcc -o server server.c 2、运行   client端:./client   server端:./server 其中,server端先运行,client端与server端的端口号必须一致并且不能与已知端口冲突(如可设为1234)。 若只在某一PC的linux系统上进行客户端服务端文件通信测试,需注意: server IP可设为回送地址127.0.0.1; 可开多个终端来模拟客户端和服务端。

socket相关3

【分享】Linux下各类TCP网络服务器的实现源代码 Linux下各类TCP网络服务器的实现源代码大家都知道各类网络服务器程序的编写步骤,并且都知道网络服务器就两大类:循环服务和并发服务。这里附上源代码来个小结吧。首先,循环网络服务器编程实现的步骤是这样的:" target="_blank"&gt;http://这种服务器模型是典型循环服务,如果不加上多进程/线程技术,此种服务吞吐量有限...

weixin_34381687的博客 238

通达信源码-主力建仓-AI投研图灵室出品

AI投研图灵室1:=3*SMA((CLOSE-LLV(LOW,75))/(HHV(HIGH,75)-LLV(LOW,75))*100,20,1)-2*SMA(SMA((CLOSE-LLV(LOW,75))/(HHV(HIGH,75)-LLV(LOW,75))*100,20,1),15,1);AI投研图灵室2W:=100-100*(HHV(HIGH,14)-CLOSE)/(HHV(HIGH,14)-LLV(LOW,14));AI投研图灵室3:=SMA(SMA(AI投研图灵室2,3,1),3,1);

qq_57222058的博客 4390

Linux内核的TCP源码入门(一)

在OSI(Open System Interconnection)体系结构中, 传输层的协议, 是实现在操作系统内核中的。 TCP是传输层的重要协议, 其源码在Linux中可以看到,本文采用的是Linux-5.19.9。Linux源码下载地址: Linux内核官网地址: 注意, 本文假设读者对TCP的相关概念有一定的了解, 未深入讲述概念.TCP协议本身逐步迭代了几个版本, 初版的TCP报文段中, 是没有"CWR"和"ECE"的: 参见 https://www.rfc-editor.org/rfc/inli

lic721的博客 3385

深入解析Xilinx IDELAYE2在FPGA时序优化中的关键作用与实战配置

本文深入解析了Xilinx FPGA中IDELAYE2模块在时序优化中的关键作用。作为精密的信号延时微调器,IDELAYE2通过可编程的抽头延迟,有效解决高速接口中数据与时钟的相位偏差问题。文章详细阐述了其必须与IDELAYCTRL配对使用的核心机制,并对比分析了FIXED、VARIABLE和VAR_LOAD三种工作模式的实战配置方法与代码示例,为FPGA工程师提供了解决时序收敛难题的关键硬件配置指南。

pear55的博客 1184

用AI免费制作30分钟电影:完整工作流与实操解析

AI视频生成技术正大幅降低影视创作门槛,但真正制作一部完整电影并非一键生成,而是涉及剧本、分镜、画面、配音、剪辑的复杂流水线。理解其基本原理,是将文本转化为连续视觉叙事的关键。对于创作者而言,掌握角色一致性控制、素材批量管理、免费额度合理分配等技术价值,能有效避免生成结果崩坏与资源浪费。该能力可广泛应用于短视频创作、独立电影预演、教学演示等场景。本文以30分钟AI电影制作为例,拆解从工具选择到成片输出的全流程,重点分析免费方案下的时间与积分成本,以及最容易翻车的环节,并给出基于实践验证的排查与优化策略。

weixin_29056865的博客 330

《Linux内核TCP/IP 实现》:协议栈源码总纲一

一.linux内核网络栈代码的准备知识 1.linux内核ipv4网络部分分层结构: BSD socket层:这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结构体现。这一部分的文件主要有: /net/socket.c /net/protocols.c INET socket层:BSD socket是个可以用于各种网络协议的接口,而当...

yexiang优快云的专栏 8265

灵境视界:基于云计算与深度学习的智能手语识别AR眼镜

灵境视界”不仅仅是一款AR眼镜,它代表了未来无障碍沟通设备的发展方向。随着云计算与深度学习技术的不断进步,未来我们将进一步优化识别算法,提升翻译精度,并扩展更多语言和手语类型,为全球听障人士提供更加全面的沟通解决方案。是一款融合了云计算与深度学习技术的增强现实(AR)眼镜,专为听障人士和手语使用者设计。这款眼镜不仅具备先进的AR显示功能,还能实时识别并翻译手语,打破听障人士与健听人士之间的沟通障碍,推动无障碍社会的建设。,让沟通无界,视界无限。

qq_62634009的博客 727

nc 源代码分析(2),流程分析

nc调试版.rar for windows by chenghao0511 步骤1,机器A输入d回车,机器B里面显示d。 步骤2,机器A输入d未回车!机器B里面输入ee回车,然后输入ff回车。 机器A里面回车。 里面显示一些debug()宏函数的信息。 vc6下nmake后顺利编译通过。可下载调试版,实验。     if (istty) {

linux-0.11调试教程 3024

手把手教你学simulink(36.1)--ACAC变换器场景示例:基于 Simulink 的矩阵变换器(Matrix Converter)

矩阵变换器(Matrix Converter, MC)是一种直接将交流电(AC)转换为不同频率和幅值的交流电的电力电子设备。与传统的基于直流环节的变频器不同,矩阵变换器不需要中间的直流环节,而是通过双向开关直接在输入和输出之间进行能量传递。这种结构使得矩阵变换器具有更高的效率、更小的体积和更好的动态响应。在本项目中,我们将使用 MATLAB/Simulink 来建模和仿真一个矩阵变换器,并分析其性能。Simulink 是一个强大的仿真工具,特别适合用于电力电子系统的建模和仿真。

MHD0815的博客 1066

某联达OA任意用户登录漏洞复现

由于`/WebService/Lk6SyncService/DirectToOthers/GetSSOStamp.asm`j接口未作限制,导致可以通过该接口调用`admin`的`cookie`值,通过对`LoginCredence`和`LoginTimestamp`的参数修改,进而实现任意登录进去系统.

千寻的博客 1589

linux简单tcp服务器,Linux下TCP网络服务器实现源代码4

进入2.6内核时代,select应该进垃圾堆了高并发服务器用select效率极低,特别是使用非阻塞IO时更是慢得一蹋糊涂改用epoll会大大改善我一个程序监听从8000到18000共计1万个端口,启动1万个LISTEN用epoll来阻塞,系统非常轻松,完全没有惊群现象epoll用法比select简单初始化:创建epoll描述字;向epoll描述字添加需要响应的套接字,初始化过程只要一次即可使用:等...

weixin_34652835的博客 275

【AUTOSAR-CanNM】-3.1-如何让ECU发出的首帧是NM帧(Tx Nm报文先于Tx App应用报文发出)

如何让ECU发出的首帧是NM帧(Tx Nm报文先于Tx App应用报文发出)

「汽车电子助手」的博客 4724

【分享】Linux下各类TCP网络服务器的实现源代码之一

Linux下各类TCP网络服务器 的实现源代码 大家都知道各类网络服务器程序的编写步骤,并且都知道网络服务器就两大类:循环服务和并发服务。这里附上源代码来个小结吧。 首先,循环网络服务器编程实现的步骤是这样的: 这种服务器模型是典型循环服务,如果不加上多进程/线程技术 ,此种服务吞吐量有限,大家都可以看到,如果前一个连接服务数据没有收发完毕后面的连接没办法处理。所以一般有多进程技术,...

一条鱼 + 一条鱼 281

ISP 芯片作为独立设备节点的驱动实现要点:多平台结构差异与工程部署策略解析

随着 ISP(Image Signal Processor)从 SoC 集成形态向独立芯片模块演进,在多摄系统、高动态场景、AI 图像前处理等需求下,ISP 作为单独设备节点进行驱动设计已成为主流趋势。尤其在多 SoC 平台(如高通、联发科、展锐、Rockchip)中,不同厂商对 ISP 的抽象方式与驱动接口处理存在显著差异。本文以实际工程为基础,从 Linux 内核下的设备注册流程、media\_entity 配置、VideoDevice 分配、数据 DMA 管理、与 sensor 的流同步控制等关键点展

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 1463

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

[zz]Linux下各类TCP网络服务器的实现源代码

http://linux.chinaunix.net/techdoc/system/2007/10/10/969538.shtml  好文不得不转: http://bbs.chinaunix.net/viewthread.php?tid=786283 作者:zhoulifa, safedead zhoulifa          发表于: 2006-7-5 23:12

pisheng的专栏 4078

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

上一篇: 成员函数指针数组,处理函数中调用:
下一篇: 简单TCP服务器端和客户端(源代码)
linuxheik
博客等级 码龄15年 865粉丝 135原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值