通过TCP协议进行C/S模式的网络通信

学习要由浅入深、由易到难,分析Linux内核中网络部分就要从内核对外提供的socket封装接口说起,典型以TCP协议C/S方式socket通信大致过程如图所示:

(图片来源于网络)

从图中可以看到TCP服务端server的初始化过程复杂一些,就像开一个小卖铺,你要登记为个体工商户其中最重要的就是营业地址(也就是bind绑定IP地址和端口号),然后就可以开门营业了(listen),营业需要有营业员在那等着接待客户(也就是accept),这样就完成了TCP服务端server的初始化。

TCP客户端client的初始化比较简单一些,就像你要去小卖铺买东西,你只要知道小卖铺的营业地址(IP地址和端口号),就可以去买东西了(connect)。

客户端connect服务端accept对接上了,客户和营业员就可以谈生意,你一句我一句(send和recv),达成交易客户端close离场,服务端继续等着接待客户(也就是accept)。

服务端代码

接下来以一个简单代码hello/hi范例来具体了解TCP协议C/S方式socket通信代码。

首先看服务端程序代码,来一个客户就reply hi。

#include"syswrapper.h"

#define MAX_CONNECT_QUEUE 1024

int main()

{

char szBuf[MAX_BUF_LEN] = "\0";

char szReplyMsg[MAX_BUF_LEN] = "hi\0";

InitializeService();

while(1)

{

ServiceStart();

RecvMsg(szBuf);

SendMsg(szReplyMsg);

ServiceStop();

}

ShutdownService();

return 0;

}

客户端代码

然后看客户端程序代码,发送hello,接收hi。

#include"syswrapper.h"

#define MAX_CONNECT_QUEUE 1024

int main()

{

char szBuf[MAX_BUF_LEN] = "\0";

char szMsg[MAX_BUF_LEN] = "hello\0";

OpenRemoteService();

SendMsg(szMsg);

RecvMsg(szBuf);

CloseRemoteService();

return 0;

}

socket接口封装代码

以上客户端和服务端代码我们都做了简单的封装,实际上看不到具体的socket代码,具体用到socket接口的代码如下:

/********************************************************************/

/* Copyright (C) SSE-USTC, 2012 */

/* */

/* FILE NAME : syswraper.h */

/* PRINCIPAL AUTHOR : Mengning */

/* SUBSYSTEM NAME : system */

/* MODULE NAME : syswraper */

/* LANGUAGE : C */

/* TARGET ENVIRONMENT : Linux */

/* DATE OF FIRST RELEASE : 2012/11/22 */

/* DESCRIPTION : the interface to Linux system(socket) */

/********************************************************************/

/*

* Revision log:

*

* Created by Mengning,2012/11/22

*

*/

#ifndef _SYS_WRAPER_H_

#define _SYS_WRAPER_H_

#include

#include /* internet socket */

#include

//#define NDEBUG

#include

#define PORT 5001

#define IP_ADDR "127.0.0.1"

#define MAX_BUF_LEN 1024

/* private macro */

#define PrepareSocket(addr,port) \

int sockfd = -1; \

struct sockaddr_in serveraddr; \

struct sockaddr_in clientaddr; \

socklen_t addr_len = sizeof(struct sockaddr); \

serveraddr.sin_family = AF_INET; \

serveraddr.sin_port = htons(port); \

serveraddr.sin_addr.s_addr = inet_addr(addr); \

memset(&serveraddr.sin_zero, 0, 8); \

sockfd = socket(PF_INET,SOCK_STREAM,0);

#define InitServer() \

int ret = bind( sockfd, \

(struct sockaddr *)&serveraddr, \

sizeof(struct sockaddr)); \

if(ret == -1) \

{ \

fprintf(stderr,"Bind Error,%s:%d\n", \

__FILE__,__LINE__); \

close(sockfd); \

return -1; \

} \

listen(sockfd,MAX_CONNECT_QUEUE);

#define InitClient() \

int ret = connect(sockfd, \

(struct sockaddr *)&serveraddr, \

sizeof(struct sockaddr)); \

if(ret == -1) \

{ \

fprintf(stderr,"Connect Error,%s:%d\n", \

__FILE__,__LINE__); \

return -1; \

}

/* public macro */

#define InitializeService() \

PrepareSocket(IP_ADDR,PORT); \

InitServer();

#define ShutdownService() \

close(sockfd);

#define OpenRemoteService() \

PrepareSocket(IP_ADDR,PORT); \

InitClient(); \

int newfd = sockfd;

#define CloseRemoteService() \

close(sockfd);

#define ServiceStart() \

int newfd = accept( sockfd, \

(struct sockaddr *)&clientaddr, \

&addr_len); \

if(newfd == -1) \

{ \

fprintf(stderr,"Accept Error,%s:%d\n", \

__FILE__,__LINE__); \

}

#define ServiceStop() \

close(newfd);

#define RecvMsg(buf) \

ret = recv(newfd,buf,MAX_BUF_LEN,0); \

if(ret > 0) \

{ \

printf("recv \"%s\" from %s:%d\n", \

buf, \

(char*)inet_ntoa(clientaddr.sin_addr), \

ntohs(clientaddr.sin_port)); \

}

#define SendMsg(buf) \

ret = send(newfd,buf,strlen(buf),0); \

if(ret > 0) \

{ \

printf("rely \"hi\" to %s:%d\n", \

(char*)inet_ntoa(clientaddr.sin_addr), \

ntohs(clientaddr.sin_port)); \

}

#endif /* _SYS_WRAPER_H_ */

这里通过宏定义的方式对socket接口做了简单的封装,封装起来有两个好处:一是把所有和socket有关的代码放在一起便于维护和移植,另一个是使得上层代码的业务过程更清晰。当然这里与我们理解socket接口的关系不太大,能理解socket的通信过程就好。

这段代码里涉及了socket接口的相关内容,比如网络地址的结构体变量、socket函数及其参数等,需要我们仔细研究了解他们的具体作用。

sockaddr和sockaddr_in的不同作用

一般在linux环境下/usr/include/bits/socket.h或/usr/include/sys/socket.h可以看到sockaddr的结构体声明。

/* Structure describing a generic socket address. */

struct sockaddr

{

__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */

char sa_data[14]; /* Address data. */

};

这是一个通用的socket地址可以兼容不同的协议,当然包括基于TCP/IP的互联网协议,为了方便起见互联网socket地址的结构提供定义的更具体见/usr/include/netinet/in.h文件中的struct sockaddr_in。

/* Structure describing an Internet socket address. */

struct sockaddr_in

{

__SOCKADDR_COMMON (sin_);

in_port_t sin_port; /* Port number. */

struct in_addr sin_addr; /* Internet address. */

/* Pad to size of `struct sockaddr'. */

unsigned char sin_zero[sizeof (struct sockaddr) -

__SOCKADDR_COMMON_SIZE -

sizeof (in_port_t) -

sizeof (struct in_addr)];

};

sockaddr和sockaddr_in的关系有点像面向对象编程中的父类和子类,子类重新定义了父类的地址数据格式。同一块数据我们根据需要使用两个不同的结构体变量来存取数据内容,这也是最简单的面向对象编程中的多态特性的实现方法。

AF_INET和PF_INET

在/usr/include/bits/socket.h或/usr/include/sys/socket.h中一般可以找到AF_INET和PF_INET的宏定义如下。

/* Protocol families. */

...

#define PF_INET 2 /* IP protocol family. */

...

/* Address families. */

...

#define AF_INET PF_INET

...

尽管他们的值相同,但它们的含义是不同的,网上很多代码将AF_INET和PF_INET混用,如果您了解他们的含义就不会随便混用了,根据如下注释可以看到A代表Address families,P代表Protocol families,也就是说当表示地址时用AF_INET,表示协议时用PF_INET。参见我们实验室代码中的使用方法,“serveraddr.sin_family = AF_INET;”中使用AF_INET,而“sockfd = socket(PF_INET,SOCK_STREAM,0);”中使用PF_INET。

SOCK_STREAM及其他协议

在/usr/include/bits/socket_type.h可以找到“__socket_type”,不同协议族一般都会定义不同的类型的通信方式,对于基于TCP/IP的互联网协议族(即PF_INET),面向连接的TCP协议的socket类型即为SOCK_STREAM,无连接的UDP协议即为SOCK_DGRAM,而SOCK_RAW 工作在网络层。SOCK_RAW 可以处理ICMP、IGMP等网络报文、特殊的IPv4报文等。

/* Types of sockets. */

enum __socket_type

{

SOCK_STREAM = 1, /* Sequenced, reliable, connection-based

byte streams. */

#define SOCK_STREAM SOCK_STREAM

SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams

of fixed maximum length. */

#define SOCK_DGRAM SOCK_DGRAM

SOCK_RAW = 3, /* Raw protocol interface. */

#define SOCK_RAW SOCK_RAW

SOCK_RDM = 4, /* Reliably-delivered messages. */

#define SOCK_RDM SOCK_RDM

SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based,

datagrams of fixed maximum length. */

...

如上几点对于我们后续进一步理解和分析Linux网络代码比较重要,代码中涉及的其他接口及参数可以在实验过程中自行查阅相关资料。

实验指导

本实验环境见 https://www.shiyanlou.com/courses/1198#labs

以上代码可以clone linuxnet.git并参照如下指令编译执行代码:

shiyanlou:~/ $ cd cd LinuxKernel

shiyanlou:Code/ $ git clone

shiyanlou:Code/ $ cd linuxnet

shiyanlou:linuxnet/ (master) $ cd lab1

shiyanlou:lab1/ (master) $ ls

client.c server.c syswrapper.h

shiyanlou:lab1/ (master) $ make

shiyanlou:lab1/ (master*) $ ./server

recv "hello" from 127.0.0.1:58911

send "hi" to 127.0.0.1:58911

右击水平分割Xfce终端(Terminal),执行client

shiyanlou:lab1/ (master*) $ ./client

send "hi" to 0.0.0.0:60702

recv "hi" from 0.0.0.0:60702

shiyanlou:lab1/ (master*) $

本博文摘取自专栏《庖丁解牛Linux网络核心》,现在订阅,抢200个早鸟名额!

专栏说明

首先声明本专栏的目标并不是帮助大家获得立即可能使用的专业技能,而是希望能通过研究分析Linux内核中网络部分的代码实现来深刻理解互联网运作的核心机制,看完本专栏预期可以达成如下目标:

从整体上理解互联网运作的方式;

能分析上网打开一个网页的过程中互联网底层具体做了哪些工作,从而在遇到网络相关问题时能独立分析定位问题;

由于我们涉及的实验都是在Linux系统完成的,您还会进一步熟悉Linux系统;

分析Linux内核中网络部分当然也少不了对网络协议及RFC文档的讨论,相信您也能对网络标准有更多的了解。

庖丁解牛linux内核,庖丁解牛Linux网络核心相关推荐

  1. Fastsocket:高扩展性的 Socket 以及 Linux 内核的底层网络实现

    目录 简介 安装编译 测试结果 GitHub README 介绍 参加者 安装 从源安装 开关内核 系统配置 用法 合适的场景### 如何使用 演示服务器 评价 Nginx的 HAProxy 在线评估 ...

  2. linux内核和发行版本的关系,简述Linux内核和Linux发行版的区别

    做服务器运维工作,要经常和Linux的版本号打交道,但一直搞不明白Linux内核和Linux发行版到底是个啥东西.其实要理解Linux内核和Linux发行版之间的关系,只要能理解下面的关系就可以了: ...

  3. 一文看懂Linux内核!Linux内核架构和工作原理详解

    linux内核相关视频解析: 5个方面分析linux内核架构,让你对内核不再陌生 90分钟了解Linux内存架构,numa的优势,slab的实现,vmalloc的原理 手把手带你实现一个Linux内核 ...

  4. 深度:一文看懂Linux内核!Linux内核架构和工作原理详解

    简介 作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址.目前支持模块的动态装卸(裁剪).Linux内核就是基于这个策略实现的.Linux进程1.采用层次结构,每个 ...

  5. 【Linux 内核】Linux 操作系统结构 ( Linux 内核在操作系统中的层级 | Linux 内核子系统及关系 | 进程调度 | 内存管理 | 虚拟文件系统 | 网络管理 | 进程间通信 )

    文章目录 一.Linux 内核在操作系统中的层级 二.Linux 内核子系统 三.Linux 内核子系统之间的关系 一.Linux 内核在操作系统中的层级 Linux 内核 所在层级 : 整个计算机系 ...

  6. 国嵌linux内核编程,linux内核--那些年看国嵌视频学习

    1.linux系统构成 由用户空间和内核空间构成.其中用户空间由应用程序和C库:内核空间由系统调用接口.kernel.架构代码.硬件设备平台 为什么linux系统会被划分为用户空间和内核空间?处于安全 ...

  7. 深入理解Linux内核 学习Linux内核的一些建议及书记推荐

    深入理解Linux内核 学习Linux内核的一些建议_华清远见教育集团 经典书籍 待到山花烂漫时,还是那些经典在微笑. 有关内核的书籍可以用汗牛充栋来形容,不过只有一些经典的神作经住了考验.首先是5本 ...

  8. 【Linux 内核】Linux 内核源码目录说明 ① ( arch 目录 | block 目录 | certs 目录 | crypto 目录 | Documentation 目录 )

    文章目录 一.arch 目录 二.block 目录 三.certs 目录 四.crypto 目录 五.Documentation 目录 在上一篇博客 [Linux 内核]Linux 内核源码结构 ( ...

  9. 【Linux 内核】Linux 内核特性 ( 组织形式 | 进程调度 | 内核线程 | 多平台虚拟内存管理 | 虚拟文件系统 | 内核模块机制 | 定制系统调用 | 网络模块架构 )

    文章目录 一.Linux 内核特性 1.Linux 内核组织形式 2.Linux 进程调度 3.Linux 内核线程 4.Linux 内核多平台虚拟内存管理 5.Linux 虚拟文件系统 6.Linu ...

  10. Linux内核和Linux发行版(了解)

    Linux内核和Linux发行版(了解) Linux内核:Linux内核是一种开放源码的操作系统,由Linux Torvalds(Linux之父)负责维护,提供硬件抽象层.硬盘及文件系统控制及多任务功 ...

最新文章

  1. 【poj3420】 Quad Tiling
  2. Exchange Technical community discussion group
  3. 【 MATLAB 】信号处理工具箱之波形产生函数 pulstran
  4. Asp.Net+SqlServer+EntityFrameWork(项目问题总结)
  5. 【计算机网络】网络层 : IP 组播 ( IP 数据报传输方式 | 组播 IP 地址 | 组播 MAC 地址 | IGMP 协议 | 组播路由选择协议 )
  6. 中国汽车零部件行业需求预测及投资前景建议报告2022-2028年版
  7. python 反射和动态加载_Python的反射
  8. vs2017python开发_VS2017作为python开发的IDE
  9. asp.net request获取url各个部分
  10. 写项目文档比写代码难多了
  11. 生产环境服务器部署kvm虚拟化6,KVM虚拟化平台环境部署
  12. 2005-2018 年软考软件设计师 真题分享
  13. Coldfusion的基础知识
  14. Kyrie Irving邀请粉丝加入Lineage Logistics的Share A Meal运动,帮助受COVID-19疫情影响的人们
  15. The Open Group即将举办线上线下企业架构从业者峰会
  16. el-date-picker 实现禁止选择今日以后的日期,以及时间跨度不超过365天,和设置默认选择日期,解决选择当天无效问题
  17. 大公司还是大城市该怎么选择?
  18. 【PyTorch】模型 FPS 测试 Benchmark(参考 MMDetection 实现)
  19. MongoDB学习指导
  20. 目标检测(三)传统目标检测与识别的特征提取——基于HOG特征的目标检测原理

热门文章

  1. UG中怎样删除已经选好的加工模板
  2. 微信开发者工具IDE调试webview内嵌H5方式
  3. 华为Lite OS的物联网攻略
  4. 项目经理面试题及答案
  5. 转载的一片关于Mapper.xml中sql的相关技术点,供以后自己慢慢学习之用
  6. Chrome 开发者工具新功能-网络面板新增载荷(Payload)边栏
  7. EasyCVR平台接入大华EVS存储服务器,需要注意哪些事项?
  8. 计算机毕业设计JavaVue.js音乐播放器设计与实现(源码+系统+mysql数据库+lw文档)
  9. LMG3422R030RQZR 600V GaN晶体管LMG3425R030RQZR电路图
  10. SYD88811新DTM测试