`

高性能Socket服务器编程-01

阅读更多

网络编程一直都是最吸引人、最有挑战的编程领域。从这篇文章开始,达达将同大家一起向这个领域出发,并接受各种难题的挑战,你准备好了吗?

写在开始之前

在开始之前,达达有一些题外话想先跟大家说说。

在阅读这一系列的文章时,我希望大家始终记住以下几点:

1. 软件开发没有银弹,人们总是试图找到问题的唯一解和最优解,但事实是每个问题都有N种解,并且在不同情况下最优解是不一样的,如果非要说软件开发有银弹,那么这颗银弹就是人的心,是否找能到最优解,在于你是否能把握住了所有事情的平衡点。所以,请不要说某某机制最好、某某算法最优、某某架构万能、不需要再了解其他了。也请不要自以为目空一切技术,商业和盈利至上,实现途径和方式无所谓。请抱着一切皆有可能的心态看待所有事物,才有更多机会看到平衡点。

2. 语言、平台、API只是迷人眼的东西,它们好像什么都是,其实什么都不是。解决问题的关键在于设计者的心,设计者是否对要解决的问题和问题的上下文了然于心。不要把学习语言、学习平台、学习API当作目标,它们只是泥沙和工具,最终我们要建造的是房屋,所以请把目标放得更远。也不要把它们当成阻碍,因为它们生来就是要让人使用的东西,不可能形成阻碍,如果你觉得它们是阻碍,那实际上那个阻碍只在你心里。

3. 记得:Do one thing, and do it well。一次只做一件事,并且做好它。这一系列文章的基本开发和测试环境是Linux,编译器是gcc。如果你之前对Linux不是很熟悉,我建议你安装一个VMware,并安装Ubuntu桌面系统,然后apt-get install build-essential,这样你的系统里就有完整的开发环境了,gnome自带的gedit很好用,我也是这么做的。不要一上来就Linux命令行界面加vi编辑器,没必要为难自己也不要搞得自己像黑客一样。请记住,我们当前要做的是高性能socket服务器,只做这一件事,并且做好它!不是研究Linux命令行或者vi编辑器,那些等有空再慢慢研究还来的及。

4. 师傅请进门修行靠个人。文章和教程的内容其实都是转之由转的东西,如果要了解原汁原味的内容,首选应该去阅读操作系统的代码,其次是系统文档,再次才是网络教程。而经验是世界上最难传达的东西,文字只能让你形成记忆,不能让你获得经验,它只是像买彩票一样给你提供一个机会,让在你实践过程中可能会有那么一下的灵光一现,然后得出自己的结论,那才是你的真正经验。如果把生活比喻成RPG,造物主怎么可能让经验可以在玩家之间传递呢?那不是乱了套了,打RPG我们可以学各种技能,但是要得到经验就得打怪做任务,生活其实也是一样的道理。

好了废话就到此结束。
让我们开始吧!

别急,别急,勿在浮沙筑高台~~!

要开始建造我们的高性能socket服务器大厦之前,还是让我们先从泥水匠做起吧,先来了解以下泥沙和工具吧。

记得前面说的吗?一次只做一件事,并且做好它。现在我们就抛开所有杂念和对高性能socket服务器的各种猜想,先做一个最基本的socket服务器端程序。

等我们逐步熟悉了泥沙和工具,我们再杀回来逐个干掉高深莫测的服务器架构设计,这就是我们的行动计划。

这里先贴出本章的示例代码,我再根据这个代码跟大家逐步讲解socket编程的关键知识点:

socketd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define SD_PORT       10086
#define SD_BACK_LOG   10

int sd_listener_fd;

void
sd_init ()
{
        int reuse = 1;

        struct sockaddr_in addr;

        if ((sd_listener_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        {
                perror("Create listener socket failed");
                exit(-1);
        }

        if (setsockopt(sd_listener_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
        {
                perror("Setup listener socket failed");
                exit(-1);
        }

        bzero(&(addr.sin_zero), 8);

        addr.sin_family      = AF_INET;
        addr.sin_port        = htons(SD_PORT);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);

        if (bind(sd_listener_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
        {
                perror("Bind listener socket address failed");
                exit(-1);
        }

        if (listen(sd_listener_fd, SD_BACK_LOG) == -1)
        {
                perror("Listen port failed");
                exit(-1);
        }
}

void  
sd_loop ()
{
        char buf[1024];

        int ret = 0;

        int client_fd;

        int client_addr_len;

        struct sockaddr_in client_addr;

        printf("Waiting connect on port %d\n", SD_PORT);

        client_addr_len = sizeof(client_addr);

        client_fd = accept(sd_listener_fd, (struct sockaddr *)&client_addr, &client_addr_len);

        printf("Client connected\n");

        for (;;)
        {
                if ((ret = read(client_fd, buf, 1024)) == 0)
                {
                        close(client_fd);

                        printf("Client closed\n");

                        break;
                }
                else
                {
                        write(client_fd, buf, ret);
                }
        }
}

void
sd_down ()
{
        close(sd_listener_fd);

        printf("Server shutdown\n");
}

int
main (int argc, char *argv[])
{
        sd_init();

        sd_loop();

        sd_down();

        return 1;
}

上面的代码是一个简单的echo服务器,它一次只处理一个连接,在客户端退出时服务器端也跟着关闭

你可以复制上面的代码保存为socketd.c,然后打开终端,切换到文件所在目录,输入:
cc socketd.c -o socketd

不出意外的话,我们的最原始版socket服务器就编译好了。然后输入:
./socketd

服务器就启动了。另外再开一个终端,输入:
telnet localhost 10086

这时候telnet应该能连上socket服务器,你可以在telnet里面输入一些文字,然后回车,服务器应该会将你发送的内容原样返回。

当你玩腻了,就在telnet界面按住Ctrl键,然后输入“]“,回车。这时候telent会切换到命令界面,输入q,回车,退出telent。

telnet退出后,相当于客户端断开了连接,按代码逻辑上面的示例程序应该会跟着退出。

顺便说一下,像上面示例这样接受并原样返回客户端请求内容的socket服务器叫做echo服务器,名字谁取的我不知道,反正大家都这么叫。 

下面我们来分析一下这段代码,我们从大结构分析入手,再深入到每个函数的介绍。

阅读这段代码要从main函数开始,main函数逐步调用了三个sd_开头的函数,sd_init() 初始化服务器 -> sd_loop() 服务器循环处理请求 -> sd_down() 服务器关闭。

sd_init 函数中的代码是典型的服务器端socket初始化过程,socket() 创建套接字 -> bind() 绑定地址 -> listener() 开始监听端口。

sd_loop 函数中的代码则是一个简单的接收客户端请求并回发数据的示例,accept() 接受新连接 -> read() 接受请求数据 -> write()发送数据。

sd_down 函数中的代码演示了如何关闭套接字,close() 就这么简单。

下面我以函数注释的方式一一注释上面代码涉及到的系统函数,这样可以不需要附加太多废话的描述并条理清晰。
/*
 * 功能:创建socket
 * 返回:成功时,返回socket文件描述符;失败时,返回-1,可以通过errno获取错误类型
 * 参数:
 *      domain   - 地址种类,较常用的有AF_INET和AF_INET6,分别对应IPv4协议和IPv6协议
 *      type     - 套接字类型,较常用的有SOCK_STREAM和SOCK_DGRAM,分别对应TCP/IP协议和UDP协议
 *      protocol - 协议,一些特殊的套接字类型下可能会用到,但是做TCP或者UDP编程时不会用到此参数,所以我们通常传递0
 */
int socket(int domain, int type, int protocol);

/*
 * 功能:将socket绑定到指定的地址
 * 返回:成功时,返回0;失败时,返回-1,可以通过errno获取错误类型
 * 参数:
 *      sockfd  - 套接字文件描述符,就是socket函数成功时返回的那个
 *      addr    - 所要绑定到的地址,其中包含地址种类、协议族IP地址和端口号,IP地址和端口号在赋值时分别需要用htonl和htons函数进行大小端转换
 *      addrlen - 地址长度,因为我们通常用的是sockaddr_in类型地址,所以这个参数就是sizeof(struct sockaddr_in)
 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

/*
 * 功能:监听套接字上的连接
 * 返回:成功时,返回0;失败时,返回-1,可以通过errno获取错误类型
 * 参数:
 *      sockfd  - 套接字文件描述符,就是socket函数成功时返回的那个
 *      backlog - 等待连接完成的队列大小,当服务器繁忙时可能没办法一次响应所有连接请求,
 *                 这时候连接请求会被放入队列等待处理,队列满的时候,客户端才真正无法连接
 */
int listen(int sockfd, int backlog);

/*
 * 功能:接受一个新的连接
 * 返回:成功时,返回新连接的socket文件描述符;失败时,返回-1,可以通过errno获取错误类型
 * 参数:
 *      sockfd  - 监听的套接字文件描述符,就是listen函数用的那个
 *      addr    - 新连接的地址信息(指针返回)
 *      addrlen - 新连接的地址长度(指针返回)
 */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

上面的函数介绍并不是最详细也不是最权威的,我建议大家不妨在命令行下面用man命令查阅各个函数的详细文档,用法是man [函数名]。

在bind之前我们创建socket地址的时候,用到了htonl和htons函数:
/*
 * 功能:Host to Network (long),将主机上的长整型数据进行大小端转换,以适应网络规范
 */
uint32_t htonl(uint32_t hostlong);

/*
 * 功能:Host to Network (short),将主机上的短整型数据进行大小端转换,以适应网络规范
 */
uint16_t htons(uint16_t hostshort);

分别与这两个函数对应但没有出现在代码中的还有:
/*
 * 功能:Network to Host (long),将网络规范格式的长整型数据进行大小端转换,以适应主机
 */
uint32_t ntohl(uint32_t netlong);

/*
 * 功能:Network to Host (short),将网络规范格式的短整型数据进行大小端转换,以适应主机
 */
uint16_t ntohs(uint16_t netshort);

为什么数据需要进行大小端的转换呢?大小端转换又是什么呢?这就要涉及到计算机组织原理的知识了。

简单说来,我们的数据在计算机中是以二进制字节数据表示的,二进制字节数据保存在内存中时就涉及到一个实现问题,比如十进制数1,对应的字节是应该表示成1000 0000还是0000 0001呢?到底是高位在前还是低位在前对计算机来说是一个实现的问题,而我们平时阅读和书写的习惯是高位在前,所以成为大端模式,即0000 0001格式,而反之则称为小端格式。在不同厂商的CPU上,数据的存储格式是不一样的,比如IBM和SUN用的是大端格式,Intel用的则是小端格式。而当不同数据格式的主机,在网络间进行数据传输时,这个实现问题就演变成了兼容问题,而RFC规范中规定了网络通讯时的字节格式是大端格式,所以系统就提供了相应的转换函数提高程序兼容性。

对于大小端格式的详细信息,大家如果有兴趣了解可以在网上搜索,下面是一个关于大小端的有趣故事:
端模式(Endian)的这个词出自Jonathan Swift书写的《格列佛游记》。

这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从圆头开始将鸡蛋敲开的人被归为Big Endian,从尖头开始将鸡蛋敲开的人被归为Littile Endian。

小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。在计算机业Big Endian和Little Endian也几乎引起一场战争。

在计算机业界,Endian表示数据在存储器中的存放顺序。

示例代码中,在创建监听的套接字文件描述符后,还执行了这样一句代码:
setsockopt(sd_listener_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

setsockopt函数是用来设置socket的参数的,这些参数决定socket的一些表现,例如后面的章节中我们使用这个函数设置socket为无阻塞模式。而上面这行代码则是用来运行socket重用端口的,所以它必须执行在bind之前。这么设置为了防止程序在意外退出后,系统没有释放端口而导致程序无法再使用原来端口

本章的示例代码只需要走马观花看一遍,熟悉一下socket编程的大概流程,以后自己亲手实验的时候不记得怎么做了再回来查阅就可以。

没必要死记硬背这些API,只需要知到这些函数存在,它们大概干什么用的。盖楼嘛,你背各种泥沙学名和化学成分有什么用?只要懂得辨别,需要时能从手册找到查阅到具体信息就可以了。
本章总结

本章演示了一个简单的echo服务器,它只支持一次处理一个连接,并且在连接退出时,服务器端也跟着关闭。

通过这个简单的例子,我们学习了基本的socket初始化过程和连接的响应方式。

当然这些知识对于我们的远大目标“高性能socket服务器”来说是远远不够的,但是这些是基础的基础,至少我们已经迈开了第一步。

下一章我将向大家介绍如何利用IO重用,在一个进程中同时处理多个连接的请求,敬请期待。

分享到:
评论

相关推荐

    C# 高性能服务器 - 端口-心跳高性能Socket服务器

    C#作为一种现代、面向对象的编程语言,因其丰富的库支持和强大的.NET框架,成为了开发高性能Socket服务器的理想选择。本篇文章将深入探讨如何利用C#实现端口管理和心跳机制的高性能Socket服务器。 首先,我们需要...

    C#高性能服务器;端口-心跳高性能Socket服务器

    端口-心跳高性能Socket服务器"是一个综合性的技术话题,涵盖了网络编程、并发处理、心跳机制等多个方面。C#语言提供了丰富的库和工具,使开发者能够构建出稳定、高效的网络服务。在实践中,开发者需要结合理论知识和...

    Linux高性能服务器编程,linux高性能服务器编程 pdf,C,C++

    《Linux高性能服务器编程》这本书是针对那些希望深入理解并掌握Linux环境下服务器开发技术的专业人士而编写的。书中详细探讨了如何利用C和C++这两种语言,实现高效且可靠的服务器应用程序。以下是该书可能涵盖的一些...

    Linux网络编程-网络基础-socket编程-高并发服务器.pdf

    【Linux网络编程-网络基础-socket编程-高并发服务器】 在深入探讨Linux下的网络编程之前,我们首先要理解网络通信的基础概念——协议。协议是数据传输和解释的规则,它确保了不同设备之间的通信能顺利进行。例如,...

    Linux高性能服务器编程电子版

    《Linux高性能服务器编程》这本书是针对那些希望深入理解如何在Linux环境下构建高效、稳定服务器的开发者和运维人员的宝贵资源。书中的内容涵盖了广泛的Linux系统编程和服务器优化技术,旨在帮助读者提升系统的性能...

    socket网络编程--有详细的描述及源代码演示

    在IT领域,网络编程是构建分布式系统和网络应用的基础,而Socket编程是网络通信的核心技术之一。本资源包提供了一套全面的教程,旨在帮助初学者深入理解和掌握Socket网络编程,包括理论基础和实践示例。 首先,...

    黑马_Linux网络编程-网络基础-socket编程-高并发服务器

    ### 黑马_Linux网络编程-网络基础-socket编程-高并发服务器 #### 知识点概述 本篇文章旨在深入解读“黑马_Linux网络编程-网络基础-socket编程-高并发服务器”相关的核心概念和技术要点,包括网络基础知识、常用...

    Linux高性能服务器编程_随书代码

    《Linux高性能服务器编程》这本书是IT领域的经典之作,主要探讨如何在Linux环境下构建高效、稳定且可扩展的服务器应用程序。随书代码包含了作者为了讲解各种技术概念和实践案例而编写的示例程序,这些代码是深入理解...

    高性能网络编程--IO 完成端口

    ### 高性能网络编程——IO完成端口 #### 基本内容概述 本文档主要介绍了高性能网络编程中的一个重要技术——IO完成端口(IOCP)。该技术是Windows平台下的一种高级I/O机制,用于处理大量的并发I/O操作,特别适用于...

    Linux高性能服务器编程PDF和代码

    《Linux高性能服务器编程》是一本深入探讨如何在Linux操作系统上构建高效、稳定、可扩展的服务器端应用的著作。这本书不仅涵盖了理论知识,还包含了实际的代码示例,旨在帮助读者将理论与实践相结合,提升在Linux...

    socket网络编程-epoll-水平触发和边缘触发源码

    socket网络编程-epoll-水平触发和边缘触发源码。 (1)I/O多路复用技术用于监控多个TCP连接上的数据收发,而epoll就是一种在Linux上使用的I/O多路复用并支持高并发的典型技术。传统的select、poll也是I/O多路复用...

    Linux高性能服务器编程源码.zip

    在Linux系统中,高性能服务器编程是一项复杂而精细的工作,它涉及到多线程、网络通信、内存管理、并发处理、I/O模型优化等多个方面。这个名为"Linux高性能服务器编程源码.zip"的压缩包很可能是为了帮助开发者理解...

    socket异步编程--libevent的使用

    下面我们将通过一个简单的示例来说明如何使用libevent实现Socket服务器的异步编程。 ##### 1. 安装libevent ```bash wget http://monkey.org/~provos/libevent-1.4.13-stable.tar.gz tar -xzvf libevent-1.4.13-...

    《Linux高性能服务器编程》(游双)

    《Linux高性能服务器编程》这本书是Linux服务器开发领域的权威指南,由具有丰富经验的Linux软件开发工程师游双倾力打造。本书旨在深入探讨如何利用Linux系统实现高性能的服务器应用,覆盖了网络协议、服务器编程的...

    【Socket编程】--TCP异步通讯一服务器多客户端

    在IT行业中,网络编程是必不可少的一部分,特别是在分布式系统和互联网应用中。...这种技术对于构建高并发、高性能的网络服务至关重要。通过实践和学习提供的代码资源,你将能够更好地掌握这一关键技能。

    01_Linux网络编程-网络基础-socket编程-高并发服务器1

    C/S模式适用于大型、高性能的应用,如网络游戏,可以利用本地资源提高效率,但需要维护客户端软件,且安装可能引发安全问题。 - **B/S(Browser/Server)模式**:使用浏览器作为客户端,简化了客户端维护,跨平台...

    Linux高性能服务器编程.pdf

    《Linux高性能服务器编程》是Linux服务器编程领域的经典著作,由资深Linux软件开发工程师撰写,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐释了编写高性能Linux服务器应用的方法、技巧和思想...

Global site tag (gtag.js) - Google Analytics