文档章节

Head First C 第十一章 网络与套接字 创建knock-knock服务器 partII

AlexTuan
 AlexTuan
发布于 2016/04/26 20:20
字数 1085
阅读 46
收藏 2

Head First C 第十一章 网络与套接字 创建knock-knock服务器 partII

继续上一小节socket使用的内容,创建一个Knock_Knock服务器。与上一节不同之处在于,我们要实现从客户端发送数据到服务器。

封装函数

打开socket

  int open_socket_listener() {
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s == -1)
      error("Can't open socket");
    return s;
  }

绑定端口

void bind_to_port(int socket, int port) {
  struct sockaddr_in name;
  name.sin_family = PF_INET;
  name.sin_port = (in_addr_t)htons(port);
  name.sin_addr.s_addr = htonl(INADDR_ANY);
  int reuse = 1;
  if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse,
                 sizeof(int)) == -1)
    error("Can't set the reuse option on the socket");
  int c = bind(socket, (struct sockaddr *)&name, sizeof(name));
  if (c == -1)
    error("Can't bind to socket");
}

接收两个参数,已经打开的socket描述符,和端口号,函数体中包含重用端口设置,错误检查。

向客户端发送数据

int say(int socket, char *s) {
  int result = send(socket, s, strlen(s), 0);
  if (result == -1) {
    error("Error talking to the client");
  }
  return result;
}

上一节说过,向socket中写入数据,需要用send()函数,接收socket描述符,要发送的字符串,字符串长度,最后一个我也还没搞明白。我们封装的函数只需要接收socket描述符和要发送的字符串。

从客户端读取数据

int read_in(int socket, char *buf, int len) {
  char *s = buf;
  int slen = len;
  int c = recv(socket, s, slen, 0);
  while (c > 0 && s[c - 1] != '\n') {
    // move s
    s += c;
    slen -= c;
    c = recv(socket, s, slen, 0);
  }

  if (c < 0)
    return c;
  if (c == 0)
    buf[0] = '\0';
  else
    s[c - 1] = '\0';
  return len - slen;
}

上面一节我们在客户端只是接收数据,这里我们要让客户端可以发送数据到服务器。

接收参数:读取数据的socket描述符(即accept()的返回值),读取数据缓存,缓存的长度。核心调用是recv()函数,这个函数不能确保每次读到了几个字符,因此我们需要循环调用它,直到读完客户端发送的数据为止。

主函数

主函数要做的是:创建一个新的服务器套接字,端口30000,队列长度为10。如果用户输入错误,就关闭连接。

流程图

st=>start: Start
e=>end
op=>operation: 从客户端取得连接 
op1=>operation: 说“Knock knock!”
cond=>condition: 是否回答了“Who's there?”
op3=>operation: 说“Oscar”
cond2=>condition: 是否回答了“Oscar who?”
op4=>operation: 说“Oscar silly question,you get a silly answer”
cls_cnnct=>operation: 关闭连接

st->op->op1->cond
cond(yes)->op3->cond2
cond(no)->cls_cnnct->e
cond2(yes)->op4
cond2(no)->cls_cnnct->e
op4->cls_cnnct->e

主函数代码

使用封装好的函数创建套接字

  char buf[255];
  if (catch_signal(SIGINT, handle_shutdown) == -1)
    error("Can't set the interrupt handler");
  listener_d = open_socket_listener();
  bind_to_port(listener_d, 30000);
  if (listen(listener_d, 10) == -1)
    error("Can't listen");
  puts("Waiting for connection...");

这里加了一个前面用过的函数,捕捉信号。

与客户端交互

  while (1) {
    struct sockaddr_storage client_addr;
    unsigned int address_size = sizeof(client_addr);
    int connect_d =
        accept(listener_d, (struct sockaddr *)&client_addr, &address_size);
    if (connect_d == -1)
      error("Can't open storage socket");
    if (say(connect_d, "Internet Knock-Knock Protocol Server\r\nVersion "
                       "1.0\r\nKnock!Knock!\r\n>") != 1) {
      read_in(connect_d, buf, sizeof(buf));
      if (strncasecmp("Who's there?", buf, 12))
        say(connect_d, "You should say 'Who's there?'!");
      else {
        if (say(connect_d, "Oscar\r\n") != -1) {
          read_in(connect_d, buf, sizeof(buf));
          if (strncasecmp("Oscar who?", buf, 10))
            say(connect_d, "You should say 'Oscar who?'!");
          else
            say(connect_d, "Oscar silly question,you get a silly answer\r\n");
        }
      }
    }
    close(connect_d);
  }

主要操作:创建一个通信套接字,并调用封装好的say()read_in()函数来与客户端交互,如果客户端发送的消息不符合要求,则提示错误并断开连接。

并发连接处理

到目前为止,我们的服务器程序还是只能一对一连接,如果有第二个用户连接,服务器并不能做出任何响应,这显然是不可接受的,因此我们要:

为每个客户端fork()一个子进程

对于fork()我们已经很熟悉了,原理不在赘述,我们只需要在正式通信前做以下操作:

int pid = fork();
if (pid == -1)
  error("Fork process error");
if (!pid) {
  close(listener_d);
  if (say(connect_d, "Internet Knock-Knock Protocol Server\r\nVersion 1.0\r\nKnock!Knock!\r\n>") != 1) {
  //...
  }
  close(connect_d);
  exit(0);
}
close(connect_d);

注意两处:

  1. close(listener_d)表示,如果当前进程是子进程,则关闭监听socket,只做数据处理。
  2. 最后一句close(connect_d)表示,如果当前进程是父进程,则关闭通信socket,只做监听操作。

本节代码

knock knock server sample

© 著作权归作者所有

AlexTuan
粉丝 4
博文 27
码字总数 17966
作品 0
程序员
私信 提问
Head First C 第十一章 网络与套接字 创建knock-knock 服务器 part I

Head First C 第十一章 网络与套接字 创建knock-knock 服务器 part I 关于套接字 套接字是一种数据流。 用途:不同计算机通过互联网进行通信。 服务器可以与多个客户端进行通信,客户端与服务...

AlexTuan
2016/04/24
47
0
Linux多线程并发服务器编程(线程池,FTP服务器)

分享网盘下载:https://pan.baidu.com/s/1gfNCcXt 密码: irfk 内容简介 本课程从最基础的进程、线程概念讲起逐步深入,通过理论与实践结合的方式,使学员快说掌握linux多线程网络编程技术,并...

人气王子333
2018/06/26
0
0
25个必须记住的SSH命令

25个必须记住的SSH命令 OpenSSH是SSH连接工具的免费版本。telnet,rlogin和ftp用户可能还没意识到他们在互联网上传输的密码是未加密的,但SSH是加密的,OpenSSH加密所有通信(包括密码),有...

guojianwei2008
2012/11/13
0
0
简单谈一点linux内核中套接字的bind机制--数据结构以及端口确定

众所周知,创建一个套接字可以bind到一个特定的ip地址和端口,实际上套接字这一概念代表了TCP/IP协议栈的应用层标识,协议栈中的应用层就是通过一个ip地址和一个端口号标识的,当然这仅仅是对...

晨曦之光
2012/04/10
1K
0
python的网络编程

一、系统和网络 1、系统 操作系统: (Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的...

技术小阿哥
2017/11/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

mysql-connector-java升级到8.0后保存时间到数据库出现了时差

在一个新项目中用到了新版的mysql jdbc 驱动 <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId>     <version>8.0.18</version> ......

ValSong
今天
5
0
Spring Boot 如何部署到 Linux 中的服务

打包完成后的 Spring Boot 程序如何部署到 Linux 上的服务? 你可以参考官方的有关部署 Spring Boot 为 Linux 服务的文档。 文档链接如下: https://docs.ossez.com/spring-boot-docs/docs/r...

honeymoose
今天
6
0
Spring Boot 2 实战:使用 Spring Boot Admin 监控你的应用

1. 前言 生产上对 Web 应用 的监控是十分必要的。我们可以近乎实时来对应用的健康、性能等其他指标进行监控来及时应对一些突发情况。避免一些故障的发生。对于 Spring Boot 应用来说我们可以...

码农小胖哥
今天
9
0
ZetCode 教程翻译计划正式启动 | ApacheCN

原文:ZetCode 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远。 ApacheCN 学习资源 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 ...

ApacheCN_飞龙
今天
5
0
CSS定位

CSS定位 relative相对定位 absolute绝对定位 fixed和sticky及zIndex relative相对定位 position特性:css position属性用于指定一个元素在文档中的定位方式。top、right、bottom、left属性则...

studywin
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部