Linux中BSD套接口开发的基础介绍

这是关于如何用各种可以得到的接口为Linux开发网络程序的系列文章的第一篇。就像大多数Unix-based的操作系统一样,Linux支持将TCP/IP作为本地的网络传输协议。在这个系列中,我们假定你已经比较熟悉Linux上的C编程和Linux的一些系统知识诸如signals,forking等等。

  这篇文章是关于如何用BSD套接口创建网络程序的基础介绍 。在下一篇中,我们会解决涉及到建立(网络)deamon进程的问题。而且今后的文章我们还会涉及到使用远程过程调用(RPC),以及用CORBA/distributed objects进行开发。

  一、TCP/IP的基础介绍

  TCP/IP协议族允许两个运行在同一台电脑或者由网络连接在一起的两台电脑上的程序进行通讯。这个协议族是专门为了在不可靠的网络上进行通讯设计的。TCP/IP允许两个基本的操作模式——面向连接的可靠的传输(指TCP)和无连接的(connectionless)不可靠的传输(UDP)。

  TCP提供带有对上层协议透明的中继功能的,顺序的,可靠的,双向的(bi-directional),以连接为基础的字节传输流。TCP将你的信息分割成数据报(不大于64kb)并保证所有的数据报无误的按照顺序都到达目的地。由于以连接为基础,所以一个虚拟连接必须在一个网络实体(network entity)和另一个之间进行通信前建立。UDP相反则提供一个(非常快的)无连接的不可靠消息传输(消息的大小是一个确定的最大长度)。

  为了使程序间可以相互通信,不论他们是在同一个机器(通过loopback接口)还是不同主机,每一个程序都必须有独立的地址。

  TCP/IP地址由两部分组成——用来辨别机器的IP地址和用来辨别在那台机器上的特定程序的端口地址。

  地址可以是点分(dotted-quad)符号形式的(如,127.0.0.1)或者是主机名形式的(如,www.csdn.net)。系统可以使用/etc/hosts或DNS域名服务(如果可以获得的话)进行主机名到点分符号地址(也就是IP地址)的转换。

  端口从1号开始编号。1和IPP0RT_RESERVED(在/usr/include/netinet/in.h中定义,通常为1024)之间的段口号保留给系统使用(也就是说,你必须以root的身份建立一个网络服务来绑定这部分的端口)。

  最简单的网络程序大都用的客户-服务器模型。一个服务进程等待一个客户进程连接他。当连接建立时,服务器代表客户执行特定的任务,通常这这以后连接就中断了。

 二、使用BSD套接口界面

  最通行的TCP/IP编程方法就是使用BSD套接口界面编程。通过它,网络端点(network endpoints)(IP地址和端口地址)以套接口(sockets)的形式出现。

  这套套接口IPC(interprocess communication,进程间通讯)设施(从4.2BSD开始引入)的设计是为了能让网络程序的设计能够独立于不同的底层通信设施。

  1、建立一个服务器程序

  要使用BSD界面建立一个服务器程序,你必须通过以下步骤:

  (1)通过函数socket()建立一个套接口
  (2)通过函数bind()绑定一个地址(IP地址和端口地址)。这一步确定了服务器的位置,使客户端知道如何访问。
  (3)通过函数listem()监听(listen)端口的新的连接请求。
  (4)通过函数accept()接受新的连接。

  通常,维护代表了客户的请求可能需要花费相当长的一段时间。在处理一个请求时,接收和处理新的请求也应该是高效的。达到这种目的的最通常的做法是让服务器通过fork()函数拷贝一份自己的进程来接受新的连接。

  以下的例子显示了服务器是如何用C实现的:

/*
 * Simple "Hello, World!" server
 * Ivan Griffin

*/

/* Hellwolf Misty translated */

#include  /* */
#include  /* exit() */
#include  /* memset(), memcpy() */
#include  /* uname() */
#include
#include  /* socket(), bind(),
               listen(), accept() */
#include
#include
#include
#include  /* fork(), write(), close() */

/*
 * constants
 */
const char MESSAGE[] = "Hello, World!\n";
const int BACK_LOG = 5;

/*

*程序需要一个命令行参数:需要绑定的端口号
 */
int main(int argc, char *argv[])
{
  int serverSocket = 0,
    on = 0,
    port = 0,
    status = 0,
    childPid = 0;
  struct hostent *hostPtr = NULL;
  char  hostname[80] = "";
  struct sockaddr_in serverName = { 0 };

  if (2 != argc)
  {
    fprintf(stderr, "Usage: %s \n",
  argv[0]);
    exit(1);
  }
  port = atoi(argv[1]);
/ *
  *socket()系统调用,带有三个参数:
  *    1、参数domain指明通信域,如PF_UNIX(unix域),PF_INET(IPv4),
  *      PF_INET6(IPv6)等
  *    2、type指明通信类型,最常用的如SOCK_STREAM(面向连接可靠方式, 
  *      比如TCP)、SOCK_DGRAM(非面向连接的非可靠方式,比如UDP)等。
  *    3、参数protocol指定需要使用的协议。虽然可以对同一个协议
  *      家族(protocol family)(或者说通信域(domain))指定不同的协议
  *      参数,但是通常只有一个。对于TCP参数可指定为IPPROTO_TCP,对于
  *      UDP可以用IPPROTO_UDP。你不必显式制定这个参数,使用0则根据前
  *      两个参数使用默认的协议。  
  */ 
  serverSocket = socket(PF_INET, SOCK_STREAM,
  IPPROTO_TCP);
  if (-1 == serverSocket)
  {
    perror("socket()");
    exit(1);
  }
 
  /*
   * 一旦套接口被建立,它的运作机制可以通过套接口选项(socket option)进行修改。   
   */

  /*
   * SO_REUSEADDR选项的设置将套接口设置成重新使用旧的地址(IP地址加端口号)而不等待
   * 注意:在Linux系统中,如果一个socket绑定了某个端口,该socket正常关闭或程序退出后,
   * 在一段时间内该端口依然保持被绑定的状态,其他程序(或者重新启动  的原程序)无法绑定该端口。
   *
   * 下面的调用中:SOL_SOCKET代表对SOCKET层进行操作
   */
  on = 1;

  status = setsockopt(serverSocket, SOL_SOCKET,
  SO_REUSEADDR,
    (const char *) &on, sizeof(on));

  if (-1 == status)
  {
    perror("setsockopt(...,SO_REUSEADDR,...)");
  }

  /* 当连接中断时,需要延迟关闭(linger)以保证所有数据都
   * 被传输,所以需要打开SO_LINGER这个选项
   * linger的结构在/usr/include/linux/socket.h中定义:
   *  struct linger
   *  {
   *   int l_onoff;  /* Linger active */
   *   int l_linger;  /* How long to linger */
   *  };

  *  如果l_onoff为0,则延迟关闭特性就被取消。如果非零,则允许套接口延迟关闭。
   *  l_linger字段则指明延迟关闭的时间
   */

  {
    struct linger linger = { 0 };

    linger.l_onoff = 1;
    linger.l_linger = 30;
    status = setsockopt(serverSocket,
  SOL_SOCKET, SO_LINGER,
  (const char *) &linger,
  sizeof(linger));

    if (-1 == status)
    {
      perror("setsockopt(...,SO_LINGER,...)");
    }
  }

  /*
   * find out who I am
   */

  status = gethostname(hostname,
  sizeof(hostname));
  if (-1 == status)
  {
    perror("gethostname()");
    exit(1);
  }

  hostPtr = gethostbyname(hostname);
  if (NULL == hostPtr)
  {
    perror("gethostbyname()");
    exit(1);
  }

  (void) memset(&serverName, 0,
  sizeof(serverName));
  (void) memcpy(&serverName.sin_addr,
  hostPtr->h_addr,
  hostPtr->h_length);
   /*
    *h_addr是h_addr_list[0]的同义词,
    *  h_addr_list是一组地址的数组
    *长度为4(byte)代表一个IP地址的长度
    */
/*
 * 为了使服务器绑定本机所有的IP地址,
 * 上面一行代码需要用下面的代码代替
 * serverName.sin_addr.s_addr=htonl(INADDR_ANY);
 */

  serverName.sin_family = AF_INET;
  /* htons:h(host byteorder,主机字节序)
   *     to n(network byteorder,网络字节序
   *     s(short类型)
   */
  serverName.sin_port = htons(port);
/* 在一个地址(本例中的serverSocket)被建立后
 * 它就应该被绑定到我们获得的套接口。
 */
  status = bind(serverSocket,
 (struct sockaddr *) &serverName,
    sizeof(serverName));
  if (-1 == status)
  {
    perror("bind()");
    exit(1);
  }
/* 现在套接口就可以被用来监听新的连接。
 * BACK_LOG指定了未决连接监听队列(listen queue for pending connections)
 * 的最大长度。当一个新的连接到达,而队列已满的话,客户就会得到连接拒绝错误。

* (这就是dos拒绝服务攻击的基础)。
 */
  status = listen(serverSocket, BACK_LOG);
  if (-1 == status)
  {
    perror("listen()");
    exit(1);
  }
/* 从这里开始,套接口就开始准备接受请求,并为他们服务。
 * 本例子是用for循环来达到这个目的。一旦连接被接受(accpepted),
 * 服务器可以通过指针获得客户的地址以便进行一些诸如记录客户登陆之类的
 * 任务。
  for (;;)
  {
    struct sockaddr_in clientName = { 0 };
    int slaveSocket, clientLength =
  sizeof(clientName);

    (void) memset(&clientName, 0,
  sizeof(clientName));

    slaveSocket = accept(serverSocket,
  (struct sockaddr *) &clientName,
  &clientLength);
    if (-1 == slaveSocket)
    {
      perror("accept()");
      exit(1);
    }

    childPid = fork();

    switch (childPid)
    {
    case -1: /* ERROR */
      perror("fork()");
      exit(1);

    case 0: /* child process */

      close(serverSocket);

      if (-1 == getpeername(slaveSocket,
  (struct sockaddr *) &clientName,
  &clientLength))
      {
        perror("getpeername()");
      }
      else
      {
      printf("Connection request from %s\n",
          inet_ntoa(clientName.sin_addr));
      }

      /*
       * Server application specific code
       * goes here, e.g. perform some
       * action, respond to client etc.
       */
      write(slaveSocket, MESSAGE,
  strlen(MESSAGE));
        /* 也可以使用带缓存的ANSI函数fprint,
        * 只要你记得必要时用fflush刷新缓存
        */
      close(slaveSocket);
      exit(0);

    default: /* parent process */
      close(slaveSocket);/* 这是一个非常好的习惯
                * 父进程关闭子进程的套接口描述符
                * 正如上面的子进程关闭父进程的套接口描述符。
                */               
    }
  }

  return 0;
}

(0)

相关推荐

  • Linux中BSD套接口开发的基础介绍

    这是关于如何用各种可以得到的接口为Linux开发网络程序的系列文章的第一篇.就像大多数Unix-based的操作系统一样,Linux支持将TCP/IP作为本地的网络传输协议.在这个系列中,我们假定你已经比较熟悉Linux上的C编程和Linux的一些系统知识诸如signals,forking等等. 这篇文章是关于如何用BSD套接口创建网络程序的基础介绍 .在下一篇中,我们会解决涉及到建立(网络)deamon进程的问题.而且今后的文章我们还会涉及到使用远程过程调用(RPC),以及用CORBA/dis

  • Linux操作系统中BSD套接口开发的基础介绍

    这是关于如何用各种可以得到的接口为Linux开发网络程序的系列文章的第一篇.就像大多数Unix-based的操作系统一样,Linux支持将TCP/IP作为本地的网络传输协议.在这个系列中,我们假定你已经比较熟悉Linux上的C编程和Linux的一些系统知识诸如signals,forking等等. 这篇文章是关于如何用BSD套接口创建网络程序的基础介绍 .在下一篇中,我们会解决涉及到建立(网络)deamon进程的问题.而且今后的文章我们还会涉及到使用远程过程调用(RPC),以及用CORBA/dis

  • python中的mock接口开发示例详解

    什么是mock? mock在翻译过来有模拟的意思.它允许您用模拟对象替换您的系统的部分,并对它们已使用的方式进行断言. Mock通常是指,在测试一个对象时,我们构造一些假的对象来模拟与其交互.而这些Mock对象的行为是我们事先设定且符合预期.通过这些Mock对象来测试对象在正常逻辑,异常逻辑或压力情况下工作是否正常,Mock的行为固定,它确保当你访问该Mock的某个方法时总是能够获得一个没有任何逻辑的直接就返回的预期结果.Mock接口就是用一些合理的手段构造对象去模拟真实接口. import f

  • SpringBoot 接口开发教程(httpclient客户端)

    目录 SpringBoot接口开发 服务端 客户端post请求 get请求 SpringBoot之httpclient使用 引入相关依赖 编写相关工具类 业务代码中使用 SpringBoot接口开发 服务端 @RestController @RequestMapping("/landary") public class landaryController { @RequestMapping("adduser") public JSONObject addUser(@

  • linux中docker的安装教程

    Docker 软件包已经包括在默认的 CentOS-Extras 软件源里.因此想要安装 docker,只需要运行下面的 yum 命令: [root@localhost ~]# yum install docker 启动 Docker 服务 安装完成后,使用下面的命令来启动 docker 服务,并将其设置为开机启动: [root@localhost ~]# service docker start [root@localhost ~]# chkconfig docker on (LCTT 译注:

  • Linux中Redis安装部署的操作步骤

    目录 1. 下载redis 2.解压文件 3.将其移动到安装目录 4. 执行make 编译 5. 进行安装 6.修改redis.conf配置 7.启动redis 8.查看 ps -aux | grep redis 9.关闭redis 10.查看redis 日志 总结 最近由于项目所需redis 进行数存储,于是得在服务器安装一个redis 1. 下载redis 直接使用wget 拉取,也可以直接去官网下载 redis官网 [root@install_folder]# wget http://do

  • 一文带你深入理解Linux中的nohup命令

    目录 前言 nohup是什么 nohup语法规则 nohup使用方法 后台运行命令 标准输出重定向到文件 标准错误输出重定向到文件 将标准输出和标准错误输出都重定向到文件 nohup后台进程管理 总结 前言 当我们在Linux或Unix系统上执行一个长时间运行的命令或脚本时,我们通常会遇到一个问题,那就是在终端关闭或者退出后,该进程也会随之停止运行.在这种情况下,我们需要一种方式来让进程在后台运行,而不受终端关闭的影响.这时,nohup命令就派上用场了. nohup是什么 nohup是Linux

  • Linux 命令查询小程序中的 WePY 云开发实践

    大家好,今天我来为大家分享一下, Linux 命令查询小程序中的 WePY 云开发实践. Why WePY 首先,先分享一下为什么要选择 WePY ? 在项目开始进行选型的时候,我可选的底层框架有 WePy.MPVue.Taro.MinUI,这些框架都是工程化做得很好的框架,可以帮助小程序项目长期进行维护.其中,Taro 因为采用的是我所不熟悉的 React ,所以从一开始就被排除.MPVue 我看了以后,它更多是给 Web 开发者提供小程序转化工具,而不是给小程序开发者提供类 Vue 工具,所

  • Linux中gpio接口的使用方法示例

    前言 Linux内核中gpio是最简单,最常用的资源(和 interrupt ,dma,timer一样)驱动程序,应用程序都能够通过相应的接口使用gpio,gpio使用0-MAX_INT之间的整数标识,不能使用负数,gpio与硬件体系密切相关的,不过linux有一个框架处理gpio,能够使用统一的接口来操作gpio.在讲gpio核心(gpiolib.c)之前先来看看gpio是怎么使用的 使用gpio 使用gpio接口需要包含#include <linux/gpio.h> ,在驱动中使用延时函数

  • c#使用微信接口开发微信门户应用中微信消息的处理和应答

    微信应用如火如荼,很多公司都希望搭上信息快车,这个是一个商机,也是一个技术的方向,因此,有空研究下.学习下微信的相关开发,也就成为计划的安排事情之一了.本系列文章希望从一个循序渐进的角度上,全面介绍微信的相关开发过程和相关经验总结,希望给大家了解一下相关的开发历程.本篇随笔主要基于上一篇<c#使用微信接口开发微信门户应用>的基础上进行深入的介绍,介绍微信消息的处理和应答的过程. 1.微信的消息应答交互 我们知道,微信的服务器架起了客户手机和开发者服务器的一个桥梁,通过消息的传递和响应,实现了与

随机推荐