精华内容
下载资源
问答
  • 单片机教程(C语言编程网
  • Linux入门教程(C语言编程网
  • 数据结构教程(C语言编程网
  • 广告云服务器1核心2G在1999年的第一年,有许多流行的云产品可以满足您的云需求2018 10图片来自网络c语言向熟练的公众人士的介绍颜小林c是一种简单的语言,它是大多数程序员的入门语言,那么c语言的通用编程规范是...

    818866380deffcc02283a13b1182a899.png

    dkdbsg2s9q.jpg

    广告

    云服务器1核心2G在1999年的第一年,有许多流行的云产品可以满足您的云需求

    iphf14tvtg.jpeg

    2018 10图片来自网络c语言向熟练的公众人士的介绍颜小林c是一种简单的语言,它是大多数程序员的入门语言,那么c语言的通用编程规范是什么? 1头文件: 1.头文件中适合放置接口的语句,不适合放置2.头文件应以稳定的方向包括在内,产品取决于平台,平台取决于标准库3.禁止.ch文件包含未使用的头文件4.每个.c ...

    版权声明: 本文是博客作者的原始文章. 转载时请指出博客地址: https: blog.csdn.netzy010101articledetails803929028051 c语言编程类似于传统的c语言编程. 当然,某些数据类型是不同的. 我们知道8051支持强大的位寻址功能,因此8051 c语言编程会添加一些新的数据类型,以免浪费8051的功能. 添加了以下内容...

    n3gqao26qe.jpeg

    1-810-jpg_6-1080-0-0-1080.jpg

    c语言是美国贝尔实验室的研究人员Dennis Ritchie在b语言的基础上开发的. 它最初是用作开发语言来转换unix操作系统的. 开发中,c开始被移植到其他操作系统平台,成为一种独立的编程语言. 自1970年代以来,许多编程语言一直受到程序员的青睐. 虽然...

    刚开始学习时,存在很多疑问,例如如何使用指针,如何匹配结构和指针,例如功能参数的要求以及如何更新io端口的数据. 实时. 如果您重新学习C语言,则必须学习很长时间才能系统地理解它. 本文将简单地整理出难以记住的知识点. 1. #ifdef和#ifndef #ifdef标识符a如果定义了标识符a,则编译程序...

    8ceh6hmmu1.png

    tiobe网站之前以python发行了2018编程语言. 我以为2019编程语言仍然会是它,但最终是C语言赢得了2019编程语言. 今年,C语言的年增长率为2.4%,第二位是C#(+ 2.1%),Python(+ 1.4%)和Swift(+ 0.6%). 为什么C语言仍然很流行?这种趋势背后的主要驱动力是物联网(IoT)和当今发布的大量小型智能设备. c语言正在被应用...

    4ksn4i81c6.jpeg

    C语言规定将“ 0”用作字符串结尾标记. 它是由系统自动添加的,因此字符常量“ b”实际上包含两个字符b 0,并且不能将其分配给字符变量. 4.忽略“ =”和“ =”之间的区别. 在c语言中,“ =”是赋值运算符,而“ ==”是关系运算符. 在此代码中,前者用于比较,后者用于分配. ? 5.忘记分号几乎用于所有编程语言...

    0tc92ejr70.png

    4bee3055ca53b60c86b5135903536218.png

    c语言获得了2019年编程语言奖. 每个人都认为python将连续第二年成为年度编程语言. 但这一次是由资深c语言获得该奖项,其年增长率为2.4%. 其次是c#(+ 2.1%),python(+ 1.4%)和swift(+ 0.6%). 为什么C语言仍然很流行?推动这一趋势的主要因素是物联网(iot)和当今发布的大量小型智能设备. c适用于性能至关重要的...

    dt5iygjlbe.png

    当然,除了c语言数据类型不同. 说明: unsigned的英语直译是unsigned,负数的符号是​​负号...

    tr22j5zgcq.png

    在c语言中c语言入门经典,定义时使用a,这意味着a数组中有5个元素. 下标从0开始,数组中的最后一个元素为a,不存在a. 7.定义数组时变量的滥用?数组名称后面的方括号内是常量表达式,其中可以包括常量和符号常量. 也就是说,c不允许动态定义数组大小. 8.地址运算符和使用错误?在c中,代表正确地址的数组名称应为: 9.同时,定义了形式参数和功能...

    togmhymddm.jpeg

    今天关于编程语言的讨论,为什么会有这么多不同的编程语言?为什么会有新的编程语言? ? https: v.qq.comxpagea0821r4y8o0.html1.3 ...的经济... c ++,java,c#声明性语言ml,haskell,序言冯·诺伊曼语言fortran,c面向对象的语言c ++,c#,java ,ruby脚本语言awk,javascript,perl,php,python,ruby,tcl ...

    i_0_1201573336x3208986252_15.jpg

    每个人都认为python将连续第二次获得tiobe年度编程语言的称号. 但这一次,它是一种优秀的旧编程语言c,以2.4%的年增长率获得了冠军. 其次是c#(+ 2.1%),python(+ 1.4%)和swift(+ 0.6%). 为什么C语言这么热? Tiobe认为,这一趋势背后的主要驱动力是物联网...

    3j0bsdb4ud.jpeg

    c语言是几乎所有编程语言的开拓者和灵感之源. Perl,php,python和ruby都写在其中. 像Microsoft Windows,Mac OS X和gnulinu一样的操作系统都依赖于It编写. 作为一种基本语言,想学习编程但没有基础的朋友,C语言可以成为您入门的基本语言之一!那么我们如何学习它并为编程奠定坚实的基础呢? ...

    2jlv6osv4a.png

    指导: 一般学习计算机语言的第一个基于计算机的课程(顾名思义是“基于计算机”,基于计算机,您太漂亮了)是在屏幕上输出“ hello world”,本章也不例外. 3.1你好,世界!本节与读者一起编写第一段c语言程序,其过程是如此详尽. 3.1. 1创建c语言源文件打开dev c ++,在上方菜单栏中选择“文件->新建->源代码”,例如...

    ys1jc3g8lv.png

    注意: “变量”和“功能”是程序的一部分. 如果读者目前无法理解其含义,可以将其放在一旁. 1.3. 2基本计算机组件计算机通常称为计算机. 它的核心组件是CPU,内存,存储器,网卡,显示器,键盘,鼠标等. 本文与c语言的引入有关,因此您只需要知道所有操作都在cpu中执行即可. c语言程序由指令,变量,常量等组成. 指令在cpu上运行...

    v2-3b17a10f55e5624eb8407b961ea73b27_b.jpg

    定义一个字符数组. 字符数组实际上是一系列字符(即字符串)的集合. 在c语言中,没有特殊的字符串变量2. 分配时,可以将字符串直接分配给字符Array,也可以不指定数组3的长度. 字符串始终以0作为字符串的结尾. 数组的长度大于字符串的长度(字符串的长度不包括0)1server.c#include#include#include#include #include ...

    xvh3p1ai8o.png

    简介: C语言程序如何工作?首先,需要将其编译并链接到可执行文件中,然后才能在不同的环境中运行. 该“环境”是指例如计算机,移动电话,路由器c语言入门经典,蓝牙扬声器等. 在智能设备中,编译器已启动了密钥桥接功能. 本章主要从分析C语言编译的整个过程开始,然后介绍常用的编译器工具,最后介绍本系列博客使用的免费开发软件...

    ml16m6365r.jpeg

    c语言的影响不仅限于此,诸如c ++,java,c#,python之类的编程语言也在c语言的基础上开发. 这也是为什么将C语言用作大学计算机教学的入门语言的原因. 1977年,为了将unix操作系统推广到其他计算机,Dennis Ritchie发布了C语言编译文本“C语言编译器”. “,这使得C语言可以在其他机器上移植...

    python的第一个缺点是它运行缓慢,与C程序相比,它非常慢. 由于Python是一种解释型语言,因此您的代码将逐行转换为CPU在执行过程中可以理解的机器代码. 此翻译过程非常耗时,因此非常慢. C程序直接编译为CPU可以在运行之前执行的机器代码,因此它非常快. 任何编程语言都有自己的一套语法. 编译器或解释器负责遵循语法的程序.

    编程语言的分类可以从三个角度来看: 角度1: 编译和解释: 也就是说,源程序的每个语句都被编译成机器语言并保存为二进制文件,以便计算机可以以机器语言直接运行此程序的优点是执行速度快. 缺点: 开发效率低,不能跨平台(如c,c ++等)解释: 在运行时只有的解释为机器语言对计算机...

    pid的缩写. 系统确保每个pid在特定时刻都是唯一的. 1.1分配进程ID默认情况下,内核将进程ID的最大值限制为32768. 您可以在此处设置procsyskernelpid_max. 短暂之后...调用方从fork()返回,它继续运行. 当前进程是父进程,成功创建的进程是子进程. 在父进程中成功进行fork()调用将在子进程fork()调用中返回子进程的pid ...

    本文来自电脑杂谈,转载请注明本文网址:

    http://www.pc-fly.com/a/jisuanjixue/article-210653-1.html

    展开全文
  • 网络编程(socket C语言编程

    千次阅读 2019-03-09 17:53:55
    socket C语言编程,看似简单,一个客户端,一个服务端。可是遇到阻塞时,怎么办?需要异步处理,你会吗? 没关系,我们可以学习。 下面是一些不错的网络编程的资源。 (1)https://beej.us/guide/bgnet/ Beej's ...

    socket C语言编程,看似简单,一个客户端,一个服务端。可是遇到阻塞时,怎么办?需要异步处理,你会吗?

    没关系,我们可以学习。

    下面是一些不错的网络编程的资源。

    (1)https://beej.us/guide/bgnet/  

    Beej's Guide to Network Programming
    Using Internet Sockets

    这是网络编程的非常好的教材!极力推荐。

    (2)https://en.wikipedia.org/wiki/Berkeley_sockets

    维基百科“Berkeley sockets”词条。作为socket基础学习,非常合适。

     

     

     

     

    展开全文
  • Linux下C语言编程之网络编程

    千次阅读 2015-12-08 09:34:38
    简要总结了Linux下C语言网络编程的主要只是,包括TCP通信和UDP通信


    一、计算机网络体系概述

    1、TCP/IP协议与OSI协议的体系结构

    国际标准化组织ISO制定出了著名的开放系统互连基本参考模型OSI/RM,简称OSI模型。但是实际上,TCP/IP协议在事实上占有市场。所以TCP/IP成了事实上的通用标准。而OSI模型被保存下来,用于作为理解网络的一种参考。OSI的七层协议体系结构的概念清楚,理论也比较完善,但是它复杂又不实用。TCP/IP是一个四层的体系结构。为了便于理解,综合OSI和TCP/IP的优点,采用一种五层体系协议来解释。

    以下从上至下对于网络各层的功能做一个简单的介绍。

    (1)应用层

    应用层是体系结构中的最高层。应用层直接为应用进程(程序)提供服务。这些服务按其向应用程序提供的特性分成组,并称为服务元素。有些可为多种应用程序共同使用,有些则为较少的一类应用程序使用。常用的应用层协议有如下几种:

    DNS域名系统(Domain Name System)

    功能:用于实现网络设备名字到IP地址映射的网络服务。

    FTP文件传输协议(File Transfer Protocol)

    功能:用于实现交互式文件传输功能。

    SMTP简单邮件传送协议(Simple Mail Transfer Protocol)

    功能:用于实现电子邮箱传送功能。

    HTTP超文本传输协议(HyperText Transfer Protocol)

    功能:用于实现WWW服务。

    SNMP简单网络管理协议(simple Network Management Protocol)

    功能:用于管理与监视网络设备。

    Telnet远程登录协议

    功能:用于实现远程登录功能。

    (2)运输层

    运输层的任务就是负责向两个主机中进程之间的通信提供服务。由于一个主机可以同时运行多个进程,因此运输层有复用和分用的功能。复用就是多个应用层的进程可以同时使用下面运输层提供的服务,分用则是运输层把收到的信息分别交付给上面应用层中相应的进程。

    运输层主要使用一下两种协议:

    传输控制协议(TCP):面向连接的,数据传输单位报文段,能够提供可靠的数据交付。

    用户数据报协议(UDP):面向无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能尽最大努力交付。

    (3)网络层

    网络层负责为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或者用户数据报封装成分组进行传送。在TCP/IP体系中,由于网络层使用IP协议,因此分组也叫作IP数据报,或简称为数据报

    (4)数据链路层

    常简称为链路层。数据链路可以粗略地理解为数据通道。物理层要为终端设备间的数据通信提供传输介质及其连接。介质是长期的,但是连接是有生存期的。在连接生存期内,收发两端可以进行一次或多次数据通信。每次通信都要经过建立通信联络和拆除通信联络两个过程。这种建立起来的数据收发关系就叫做数据链路。而在物理媒体上传输的数据难免受到各种不可靠因素的影响而产生差错,为了弥补物理层上的不足,为上层提供无差错的数据传输,就要能对数据进行检错和纠错。数据链路的建立,拆除,对数据的检错,纠错是数据链路层的基本任务。

    两个主机之间的数据传输,总是在一段一段的链路上传送的,也就是说,在两个相邻结点之间(主机和路由器之间,或者两个路由器之间)传送数据的(点对点)。这时就需要使用的专门的链路层协议,在两个相邻的结点间传送数据时,数据链路层会把网络层交付下来的IP数据报组装成,然后在两个相邻的结点间的链路上“透明”地传输帧中的数据。每一帧包括数据和必要的控制信息(如同步信息、地址信息、差错控制信息等)。

    所谓“透明”,就是指无论什么样的比特组合的数据都能够通过这个数据链路层。因此,对于数据来数,虽然数据链路层是存在的,但是数据感觉不到它。就像你无法看见一个在你面前100%透明的玻璃一样,虽然有,但不影响你看见玻璃后面的东西。

    在接收到数据时,控制信息使接收端能够知道一个帧是从哪个比特开始和到哪个比特结束的。这样,数据链路层在接收到一个帧后,就可以从帧中提取出数据部分,上交给网络层。

    控制信息还使得接收端能够检测出所收集到的帧中有无差错。如发现有错,数据链路层就简单的丢弃这个出错帧,以免浪费网络资源。如果需要改正错误,就由运输层的TCP协议来完成。

    (5)物理层

    物理层的任务就是透明的传送比特流。在物理层上所传送数据的单位是比特。也就是说,发送方发送1(或0)时,接受方应当受到1(或0)而不是0(或1)。因此物理层要考虑用多大的电压电表1或0,以及接受方如何辨识出发送方所发送的比特。物理层还要确定连接电缆的插头应该有多少根引脚以及各个引脚应如何链接。注意,传递信息所利用的一些物理媒体,如双绞线,同轴电缆,光缆等并不在物理层协议之内,而是在物理层的下面。因此也有人将物理媒体当做第0层。

    2、计算机通信过程

    下面简单总结一下应用程序的数据在各层之间的传递过程中所经历的变化。简单起见,假设连个主机是直接相连的。

    图1-1 数据在各层之间的传递过程

     

    假定主机1的应用进程APP1向主机2的应用进程APP2传送数据。APP1先将其数据交给本主机的第五层(应用层)。第五层加上必要的控制信息H5就变成了下一层的数据单元。第四层(运输层)收到这个数据单元后,加上本层的控制信息H4,再交给第三层(网络层),成为第三层的数据单元。以此类推。不过到了第二层(数据链路层)后,控制信息分成两部分,分别加在本层数据单元的首部(H2)和尾部(T2),而第一层(物理层)由于是比特流的传送,所以不再加上控制信息。注意:传送比特流时应该从首部开始传送。

    当这一串比特流离开主机1经过网络的物理媒体传送到目的主机2时,就从主机2的第一层依次上升到第五层。每一层根据控制信息进行必要的操作,然后将控制信息剥去,将该层剩下的数据单元上交给更高一层。最后,把应用进程APP1发送的数据交给目的主机的应用进程APP2,完成一次通信。

    二、TCP/IP协议

    1、TCP/UDP简介

    运输层提供了两种传送协议:传输控制协议(TCP)和用户数据报协议(UDP)。

    传输控制协议(TransferControl Protocol,TCP)是一种面向连接的协议,网络程序在使用这个协议的时候,网络可以保证客户端和服务器端的链接是可靠的,安全的。

    用户数据报协议(UserDatagram Protocol,UDP)是一种面向非连接的协议,这种协议并不能保证网络程序的链接是可靠的,所以一般采用TCP协议.

    2、数据的封包与解包

    应用程序所发送的数据称为有效载荷,为了有效载荷的顺利传送,运输层和网络层会添加一些控制信息,来保证传送的可靠性。其中与socket编程有关的最主要的项目有4个:

    源主机的IP地址(源IP):使得目的主机能够得知源主机的IP,从而能够回送数据包。

    源主机的端口号(源端口):用于标识员主机上发送数据的应用进程,以便将目的主机回传的数据交付给该进程。

    目的主机的IP地址(目的IP):使数据包能够到达指定的目的主机,并被目的主机的网卡接收。

    目的主机的端口号(目的端口):网卡接收到的数据包应该被目的主机上哪个应用进程接收和处理。

    源主机会在数据包发送到网线上之前,使用上述4个内容对有效载荷进行封包操作:在有效载荷之前加上TCP包头(最主要的是目的端口源端口),然后再在前面加上IP包头(最主要的是目标IP源IP)。目的主机在接收到数据包之后,进行封包的逆操作来解包,提取有效载荷。

    图2-1  TCP/IP数据包

    注意:不同的网络应用程序占用不同的端口号,http的端口号为80,ftp的端口号为21,POP3的端口号为110,SMTP的端口号为25,TELNET的端口号为23等等。目的IP一般来源于应用程序的用户输入。一般来说,客户端程序不会固定的占用一个端口号,当它需要与别的机器进行通信时,会向操作系统请求并获得一个临时的源端口号。根据TCP/IP协议的规定,这个临时端口号是本机上尚未使用的大于1024(小于1024的端口被TCP/IP协议规定为专用的端口号,它们被各个著名的服务器所使用),小于65535(端口号由2个字节表示,最多为65535)的端口号。

    3、TCP/UDP/IP包头简介

    运输层和网络层按照固定的格式为有效载荷加上TCP/UDP包头和IP包头,下面分别介绍一下。

    3.1  TCP包头的结构

           0                                    15 16                                 31

    16位源端口号

    16位目的端口号

    32位序列号

    32位确认序列号

    4位数据偏移

    保留

    (6位)

    U

    R

    G

    A

    C

    K

    P

    S

    H

    R

    S

    T

    S Y N

    F I N

    16位窗口大小

    16位检验和

    16位紧急指针

    选项

    数据

    图2-2  TCP包头结构

    TCP包头的各个字段的含义如下表2-1所示。

     

    表2-1  TCP包头的含义

    名称

    作用

    TCP源端口

    记录源主机的端口号,源端口和源IP地址的作用是标识报的返回地址(即哪台主机的哪个进程)

    TCP目的端口

    记录目的主机的端口号,用来指明接受该报文的计算机上的应用程序的地址接口

    TCP序列号

    32位的序列号由接收端计算机使用,用来将分段的报文重组成最初的形式

    TCP确认号

    目的主机使用32位的应答域标识下一个希望收到的报文的第一个字节

    数据偏移

    指明TCP包头的大小,以32位数据结构(4字节)或“双字”为一个单位(包头后面就是有效载荷)

    保留

    该6位恒为0,为将来定义新的用途保留

    标志位

    6个标志位各控制一个功能,和上图对应,其含义分别为:紧急标志、有意义的应答标志、推、重置链接标志、同步序列号标志、完成数据发送标志

    窗口大小

    目的主机使用该域告知源主机,期望收到的TCP数据段的大小,用于TCP报文的流量控制

    校验和

    验证收到的数据是否正确

    紧急指针

    该域只有在URG标志位被置位后才有效,用来指向段内的最后一个字节位置

    选项

    至少一个字节的可变长域标识哪个选项

    数据

    最大为MSS,MSS可以在源主机和目的主机之间协商

    填充

    保证空间的可预测性、定时和规范的大小。该域加入额外的零以保证TCP包头是32的整数倍

     

    3.2  UDP包头的结构

    0                                    15 16                                 31

    16位源端口号

    16位目的端口号

    用户数据报的总长度

    校验和

    数据

    图2-3  UDP包头结构

    UDP包头的各个字段的含义如下表2-2所示。

    表2-2  IP包头的含义

    名称

    作用

    源端口

    记录源主机的端口号,源端口和源IP地址的作用是标识报的返回地址

    目的端口

    记录目的主机的端口号,用来指明接受该报文的计算机上的应用程序的地址接口

    总长度

    UDP数据包的总长度

    校验和

    验证收到的数据是否正确

     

    3.3  IP包头的结构

           0                                  15 16                               31

    4位

    版本号

    4位首部长度

    8位服务器类型

    16位总长度(字节数)

    16位标识

    3位标识

    13位片偏移

    8位生存时间(TTL)

    8位协议

    16位首部检验和

    32位源IP地址

    32位目的IP地址

    选项(如果有)

    数据

    图2-4  IP包头结构

    IP包头的各个字段的含义如下表2-3所示。

    表2-3  IP包头的含义

    名称

    作用

    版本

    IP包头中前4位标识了IP的操作版本,即IPv4或者IPv6

    首部长度

    用来指明IP包头的长度,以32位(4字节)为单位表示

    服务类型

     

    总长度

    IP包最大长度为65535字节

    16位标识

    每个IP报文被赋予唯一的16位标识,用于标识数据报的分段

    分段标志

    (3位标识)包括3个1位标志,标识报文是否允许被分段和是否使用了这些域

    分段偏移

    指出分段报文相对于整个报文开始处的偏移,该值以64位(8字节)为单位递增

    生存时间

    IP报文不允许在广域网中永久漫游。必须将其限制在一定的TTL内

    协议

    8位域指示IP包头之后(即运输层)所使用的协议,如VINES、TCP、UDP等

    校验和

    验证收到的数据是否正确

    源IP地址

    指明源计算机的IP地址

    目的IP地址

    指明目的计算机的IP地址

    填充

    为了保证IP包头的长度是32的整数倍,要填充额外的零

    4、基于TCP的通信连接

    对比上述给出的TCP和UDP的包头格式,可将UDP看成是TCP包头的简化版,所以搞清楚TCP连接建立的过程,UDP的建立过程也不难理解。以下介绍基于TCP通信的建立过程。

    4.1  通信前期—3次握手建立TCP连接

    TCP通信连接的建立需要经过三次握手,以此建立一个可靠的信息交付通路。为了更加清楚的解释三次握手的过程以及其意义,首先对TCP包头的6个标志位做进一步的解释。

    URG标志位:此标志表示TCP包的紧急指针域是有效的(即16位紧急指针域的使能开关)。用来保证TCP连接不被中断,并且督促中间层设备(例如路由器)要尽快处理这些数据。

    ACK标志位:此标志表示TCP包的应答域是有效的。就是说前面所说的TCP确认序列号将会包含在TCP数据包中。该位取值1表示应答域数据有效,0表示应答域数据无效(即32位确认序列域的使能开关)。

    PSH标志位:这个标志位表示Push操作。所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队。

    RST标志位:这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包。

    SYN标志位:此标志表示TCP包的同步序号域是有效的(即32位序列域的使能开关)。SYN标志位和ACK标志位搭配使用,用来请求建立连接。

    FIN标志位:表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。

    6个标志位各司其职,不同的组合实现不同的功能,但也并非所有组合均是合法的。不合法的标志位组合如下:

    (1) SYN和FIN同时被置1                         (2) FIN位被置1,但ACK位没有被置1

    (3) SYN和RST同时被置1                       (4) PSH位被置1,但ACK位没有被置1

    (5) FIN和RST同时被置1                         (6) URG位被置1,但ACK位没有被置1

    (7) URG和PSH同时被置1                      (8) 所有标志位都为0

    TCP连接建立时,每个步骤及作用如下所述:

    (1)当客户机请求与服务器进行通信时,会首先发送一个连接请求报文段。该报文的首部中,同步位SYN=1,同时随机选择一个初始化序列号seq=x,并指明要连接的服务器上的端口号。TCP规定,SYN报文段(即SYN=1的报文段)不能携带数据,但是仍然要消耗掉一个序号。之后,TCP客户端进程进入SYN_SENT(同步已发送)状态。

    (2)服务器收到客户机的连接请求,如果同意该连接请求,则向客户机发送确认报文段,建立连接。在确认报文段应把SYN和ACK标志位都置1,确认序列号是ack=x+1,同时服务器也为自己选择一个初试序列号seq=y。请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。这时,TCP服务器端进程进入STN_RCVD(同步收到)状态。

    (3)TCP客户端进程收到服务器的确认信息之后,还要向服务器给出确认(即对服务器确认信息的确认)。确认报文段的ACK置1,确认号ack=y+1,而自己的序号seq=x+1。TCP标准规定,ACK报文段(即ACK=1的报文段)可以携带数据。但是如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段的序号仍是seq=x+1。这时,TCP连接已经建立,客户机进入ESTABLISHED(已建立连接)状态。当服务器收到客户机的确认信息之后,也进入ESTABLISHED状态。

    经过上述3步,客户机就与服务器成功建立起一个TCP连接。上述就是经典的3次握手过程,TCP连接建立时3次握手过程如图2-5所示。

    图2-5   3次握手建立TCP连接

        三次握手的简单总结:

    第一次握手:客户机发送同步报文段(SYN=1),并随机产生seq x=12345676的数据包到服务器,服务器由SYN=1知道,客户机要求建立联机;

    第二次握手:服务器收到请求后要确认联机信息,向客户机发送应答序列号ack=x+1=12345677,SYN=1,ACK=1,并随机产生服务器的序列号seq y=7654321的包;

    第三次握手:客户机收到应答信息并检查ack的值是否等于x+1(即12345677),以及位码ack是否为1。若正确,客户机会再发送ack=y+1=7654322,ACK=1的报文段。服务器收到后确认ack(即7654322)的值与ACK=1,则连接建立成功,完成三次握手。

    SYN报文段不携带数据,但消耗序列号。ACK报文段用来携带数据,消耗序列号,且数据发送期间ACK恒为1。

    4.2  通信中期—数据的收发

    关于TCP包头中32位序列号的说明:占用4个字节。范围是[0,232-1],共232(即4284967296)个序列号。序号增加到232-1后,下一个序号就回到0.也就是说,序号使用mod232运算。TCP是面向字节流的。在一个TCP连接中传输的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始号必须在建立连接时设定。TCP包头中的32位序号字段值则指的是本报文段所发送的数据的第一个字节的序号。例如,本报文段的32位序号字段值是301,而携带的数据共有100字节。这就表明:本报文的有效数据的第一个字节的序号是301,而最后一个字节的序号是400。显然,下一个报文段(如果还有的话)的数据序号应当从401开始,即下一个报文段的32位序号字段值应为401。该字段与数据发送有密切的关系。

    例如,PC1向PC2发送数据,该数据为5000字节。该数据到达传输层,使用TCP传输协议,给每个字节加一个序列号,序列号是从[0,232-1]之间随机产生的。比如该报文的第一个字节的序列号为x,第二个字节的序列号就是x+1,最后一个字节的编号就是x+4999。传输层在传送数据时如果数据比较大会进行分段传送,假设每100个字节分一个报文片段。那么第一个报文片段的第一个字节序列号肯定是x,该报文片段最后一个字节的序列号就是x+99,那么就用x来表示整个报文片段。第二个报文片段的序列号范围是x+100—x+199,用X+100代表第二个报文片段。第三个x+200—x+299……以此类推。5000字节的数据,最终被分成50个报文片段发送。

    如果接受方正确的接收了第一个报文段就会发送一个确认信息。确认的目的就是表示准确的收到了一个报文,并通知发送方希望接受的下一个报文段的序号是什么。例如,如果接收方收到了第一个报文,该报文的有效数据的序号是x到x+99,如果接受方还想继续接受第二个报文就需要向发送方发送确认号x+100,表示我准确的收到了x+99这段数据,我希望你从x+100这个序号继续发送。

    数据收发过程的简单总结:

    接收到发送方发送的数据后,验证正确性,并告知发送方希望收到的下一个报文分组的首字节的序列号是多少。

    4.3  通信后期—4次挥手关闭TCP连接

    由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭。关闭原则是当一方完成数据发送任务后,发送一个FIN报文段(FIN=1的报文段)来终止这一方向的连接。收到一个FIN报文段只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到另外一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

    图2-6   4次挥手关闭TCP连接

    数据传输结束后,通信双方都可以释放链接。如图2-6所示,客户机和服务器都处于ESTABLISHED状态。客户机的应用程序先向服务器发出TCP连接释放报文段,并停止再发送数据,主动关闭TCP连接。客户机把连接报文段首部的FIN置1,其序列号seq=u(u的值等于客户机之前已经发送的有效数据所消耗的的最后一个字节序号加1)。这时客户机进入FIN-WAIT1(终止等待1)状态,等待服务器的确认。注意,TCP规定,FIN报文段即使不携带数据,仍然消耗掉一个序号。

    服务器收到连接释放报文段后即发出确认报文段,确认号是ack=u+1,而这个报文段(应答报文段)自己的序号是v(v的值等于服务器之前已经发送的有效数据所消耗的的最后一个字节序号加1)。之后B就进入CLOSE-WAIT(关闭等待) 状态。此时,TCP服务器进程会通知高层应用进程。因此,从客户机到服务器方向的连接就被释放了,这时的TCP连接处于半关闭状态,即客户机已经没有数据要发送了,但是服务器如果发送数据的话,客户机仍然要接收。也就是说,从服务器到客户机方向的连接并未关闭。该状态可能会持续一段时间。

    客户机收到服务器的确认报文段后,就进入FIN-WAIT-2(终止等待2)状态,等待服务器发出连接释放请求报文段。若服务器已经没有需要向客户机发送的数据,其应用进程就会通知TCP释放连接。这时服务器发出的连接释放报文段必须使FIN=1。假定服务器的序号为w(半关闭状态,服务器有可能又发送了一些数据)。服务器必须重复上次已经发送过的确认号ack=u+1。这时服务器就进入LAST-ACK(最后确认)状态,等待客户机的确认。

    客户机收到服务器的连接释放报文段后,必须对此发出确认。在确认报文中把ACK置1,确认号ack=w+1,而自己的序列号是seq=u+1(TCP标准规定,之前发送的FIN报文段要消耗一个序号)。之后客户机进入到TIME-WAIT(时间等待)状态。注意,此时服务器到客户机方向的TCP还没有释放掉。必须经过时间等待计时器设置的2MSL后,客户机才进入到CLOSED状态。时间MSL叫做最长报文寿命。早期规定MSL=2分钟,因此客户机进入TIME-WAIT状态后要经过2MSL=4分钟,才能进入到CLOSED状态,才能开始建立下一个新的连接。至此客户机和服务器双向的TCP连接断开。

    关于2MSL时间的一些补充说明:

    为什么客户机在TIME-WAIT状态必须等待2MSL的时间,理由有两个。

    第一、为了保证客户机发送的最后一个ACK报文段能够到达服务器。这个ACK报文段是有可能丢失的,因而使得处在LAST-ACK状态的服务器收不到对已经发送的FIN+ACK报文段的确认。服务器会因为超时没有接收到对FIN+ACK报文确认报文而重新发送FIN+ACK报文段,而客户机就可以在2MSL时间内收到这个重传的FIN+ACK报文段。接着客户机会重传一次确认报文,重新启动2MSL计时器。最后,客户机和服务器都进入到CLOSED状态。如果客户机在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段就立即释放连接,那么如果ACK意外丢失,服务器等待超时后,重传的FIN+ACK报文段就无法接收,因而也不会再重传ACK的确认报文段。这样服务器就无法按照正常步骤进入CLOSED状态。

    第二、防止已经失效的连接请求报文段出现在新的连接中。客户机在发送完最后一个ACK报文后,再经过2MSL时间,就可以使本连接持续时间内所产生的所有报文都从网络中消失(MSL远远大于TTL)。这样就可以使下一个新的连接中不会出现旧连接中的请求报文段。服务器只要收到了客户机发来的确认报文段,就会进入CLOSED状态。

    三、Linux下socket套接字编程

    1、TCP编程模型

    1.1 套接字socket简介

    Linux中的网络编程通过socket接口实现。socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”。所以说,所谓socket,既是一种系统调用,也是一种特殊的I/O文件。可以用【打开open】–>【读写write/read】->【关闭close】模式来操作。socket在物理上对应一个网络连接,在编程上对应一个文件描述符。当应用程序调用write向socket这种文件描述符写入数据时,数据就会被发送至网络,进而到达对方的机器。

    一个完整的socket都有一个相关描述【协议、本地地址、本地端口、远程地址、远程端口】,也就是说一个完整的socket是一个5元组。每个socket对应本地唯一的由操作系统分配的socket号(文件描述符)。

    1.2 套接字socket的分类

    (1) 流式套接字(SCOK_STREAM)。流式套接字可以提供可靠的、面向连接的通信流。它使用TCP协议。TCP协议保证了数据传输的正确性和有序性。

    (2) 数据报套接字(SOCK_DGRAM)。数据报套接字定义了一种面向无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠、无差错。

    (3) 原始套接字。原始套接字允许对底层协议,如IP或ICMP直接访问,主要用于新的网络协议实现的测试。

    1.3 TCP套接字socket编程步骤

    TCP网络编程整体步骤如图3-1所示。

     

    图3-1  TCP socket编程模型

    (1) 服务器端(server)编程步骤

    ① 调用socket创建一个不与任何网络连接相对应的套接口(socket),其返回值是一个文件描述符;

    ② 调用bind,将刚创建的socket与本机的IP地址和端口号绑定,此时该socket为一个3元组;

    (IPv4协议,本地IP,本地端口)

           ③调用listen,将socket设定为被动套接口(用于监听),专门用于监听客户端的网络连接请求;

           ④调用accept,使socket进入到可以接受网络连接请求的状态。此时服务器将进入阻塞态,直到有客户端的连接到达,accept将返回另一个socket(文件描述符),这个socket被称为连接套接口(用于通信),此套接口是一个5元组(其中的远程IP和远程端口来自于客户机发起连接请求时,发送到服务器的数据包,其实就是3次握手时第一个数据包);

    ⑤ 调用文件读取函数(如read,recv)从连接套接口接收客户端发送过来的数据。如果此时客户端并未发送数据,则read将会阻塞到有数据到达为止;

    ⑥ 对客户端的数据进行处理,之后调用write函数向连接套接口写入处理后的数据,该数据将通过网络发送至客户端;

    ⑦ 当所有的数据都已经处理完毕,将调用close函数关闭连接套接口,这将在网络上激发4次挥手终止序列,在客户机的配合下,成功关闭网络连接,结束通信。

    (2) 客户机端(client)编程步骤

    ① 调用socket创建一个不与任何网络连接相对应的套接口(socket),其返回值是一个文件描述符;

    ② 调用connect,将服务器IP和端口号作为参数传入,OS将选择一个本机尚未使用的大于1024的端口号作为本地端口号(此时,客户端的socket已经是5元组),主动发起连接请求,经过3次握手,connect将返回,此时socket已经对应上物理上的一个网络连接通道;

    ③ 调用write向socket写入数据,该数据将到达服务器,被read函数所接收;

    ④ 调用read函数,等待服务器经socket回送的处理数据;

    ⑤ 当所有数据接收完毕,调用close函数,激发4次挥手终止序列,关闭网络连接,结束通信。

    1.4 套接字编程基础知识

    (1) 套接口地址结构体

    bind()函数用来将新创建的网络套接字(设备描述符)和实际网络实体进行绑定。所谓绑定就是建立套接字设备描述符和物理网络实体信息之间的映射。对套接字设备描述符进行操作,就相当于对物理网络实体操作。而物理网络实体的信息就是用套接口地址结构体来存放的。套接口地址结构体如下:

    typedef       unsigned long          uint32_t;

    typedef       uint32_t                      in_addr_t;

    struct    in_addr

    {      

    in_addr_t             s_addr;         //存放IP地址

    };

     

    typedef       unsigned short int           sa_family_t;

    typedef       unsigned int                     uint16_t;

    typedef       uint16_t                              in_port_t;

    struct    sockaddr_in                                            //16字节,存放物理网络实体的信息(Linux套接字结构体)

    {     

     sa_family_t                 sin_family;                 //2字节,指定IP协议版本(IPv4 IPv6)

     in_port_t                     sin_port;                    //2字节,指定端口号

             struct  in_addr           sin_addr;                   //4字节,指定IP地址

             unsigned char          sin_zero[  sizeof(structsockaddr)-

       (sizeof(unsignedshortint))-

        sizeof(in_port_t)-

        sizeof(structin_addr)  ];

    };

         *******************************************************     分隔线      ********************************************************

    typedef       unsigned short int   sa_family_t;         …………………………//通用套接字接口结构体

    struct    sockaddr

    {      

    sa_family_t         sa_family;

            char                    sa_data[14];

    };

     

    struct    sockaddr_un                        …………………………………………//UNIX套接字接口结构体

    {      

    sa_family_t        sun_family;

            char                   sun_path[108];

    };


    ① 分隔线以下的套接字结构体中,sockaddr_un套接字结构体为UNIX所使用。分隔线以上的sockaddr_in套接字结构体是Linux网络编程中常用的socket套接口结构体。bind、connect等函数,可以根据套接口类型的不同,处理不同的address family(地址族、协议族),例如AF_UNIX、AF_INET等。但不同的地址族有不同套接口地址结构,例如sockaddr_un和sockaddr_in,因此需要一个统一的通用套接口地址结构体,例如sockaddr套接字结构体作为bind、connect函数的参数。这样,内核就可以通过检查sa_family的值确定套接口地址结构体的类型,然后对套接口地址结构体指针进行正确的类型转换。

    ② 要解决①中提出的问题,对于ANSI标准来说很容易,只要定义void *即可。但是socket编程早于ANSI,所以其采用的解决方法是,依然定义通用套接字接口地址结构体sockaddr(事实上,这样做不伦不类,因为sockaddr的长度与sockaddr_in相同,为16字节,但sockaddr_un却是110字节),同时这也导致了编程中调用bind等函数时,必须对第二参数(指针)进行强制类型转换,不然就会报错。

    ③ sin_addr (s_addr)用来存放IP地址,sin_port用来存放端口号,sin_family用来存放使用的IP协议版本;

    ④ struct  sockaddr_in长度为16字节,其中sin_zero长度为8字节,暂时未用,应全部初始化为0;

    ⑤ 可以通过两种方式访问32位IP地址:1)访问结构体(sin_addr);2)访问32位无符号整数(sin_addr.s_addr)。由于历史原因,sin_addr 是一个结构体,且其成员为一个union共用体,以便方便的访问A、B、C类的网络地址,但是随着无类地址的广泛使用,该union被废除,sin_addr结构体的成员变成32位整数s_addr。

    (2) 字节序列转换

    数据在内存中的存放顺序有两种,即大端序和小端序。采用大端序的系统称为大端系统,采用小端序的系统称为小端系统。英特尔的CPU就是典型的小端系统。对于数据来说,都有高位数据和低位数据,所以大小端定义如下:

    所谓大端序是指,数据在内存中的存放规则为,低位数据存放在内存的高地址位,高位数据存放在内存的低地址位。

    所谓小端序是指,数据在内存中的存放规则为,低位数据存放在内存的低地址位,高位数据存放在内存的高地址位。

    一个简单的记法:大端交叉相对,小端一一相对

    由于客户机采用的系统有可能是大端系统,也有可能是小端系统。如果一个大端(小端)系统不加处理地将数据经由网络发送给一个小端(大端)系统,则会出现错误。例如,如果发送(接收)机是小(大)端系统,则发送short型整数0x3132(先发0x32,后发0x31)到达接收机后,0x32被存放在内存低端,0x31被存放在内存高端,最终将被解释为0x3231,而不是0x3132。这样是有问题的。

    所以,在网络数据交换中,要求网络中传输的数据格式必须是网络字节序。网络字节序采用的是大端字节序。所以数据在被发送到网络上之前,需要进行数据格式的转换。当数据接到达接收机之后,接收机会按照自身系统所采用的字节顺序对接收到的数据进行相应的格式转换,以保证通信数据的正确性。

    常用的字节序列转换函数如下:

    #include <arpa/inet.h>                                                        //需要包含的头文件

    unsigned long int htonl(unsigned long int hostlong)    //将unsigned long int数据的主机字节序转换为网络字节序

    unsigned short int  htons(unsignedshort int  hostshort)    //将unsigned short int数据的主机字节序转换为网络字节序

    unsigned long int ntohl(unsigned long int netlong)      //将unsigned long int数据的网络字节序转换为主机字节序

    unsigned short int ntohs(unsigned short int netshort)      //将unsigned short int数据的网络字节序转换为主机字节序

    (3) 地址格式转换

    IP地址在计算机中(无论是客户机还是服务器)使用点分十进制的字符串形式表示的,例如192.168.1.103。但是IP协议在进行封包操作时,会将点分格式的地址字符串转换为网络字节序的整型数。而计算机收到数据包之后会进行逆操作,即将网络字节序整型转换为点分格式的IP地址。常用的地址格式转换函数:

    #include <sys/types.h>

    # include <sys/socket.h>

    # include <arpa/inet.h>

    int  inet_pton(int  af, const char *  src,  void * det)

    函数功能:将点分格式的地址字符串转换为网络字节序的整型数。

    返 回值:成功返回1,错误返回-1

    函数参数:

    -af   采用的协议类型。AF_INET(IPv4)或AF_INET6(IPv6)

    -src  点分格式的IP字符串地址

    -dst  转换后的整型变量的地址

    192.168.6.100 ================>  0x6406a8c0

     

    const char *inet_ntop(int  af,  const void *src,  char  *dst, socklen_t  cnt)

    函数功能:将网络字节序的整型数转换为点分格式的地址字符串。

    返 回值:成功返回转换后字符串的地址,错误返回NULL

    函数参数:

    -af   采用的协议类型。AF_INET(IPv4)或AF_INET6(IPv6)

    -src  网络字节序的整型变量的地址

    -dst  转换后的点分格式的IP字符串地址

    -cnt  字符串存储空间的大小

    0x6406a8c0 ================>  192.168.6.100

    (4) IP和域名的转换

    一台主机或服务器的IP地址通常很难记住,为了便于记忆,采用域名来标志网络上的一台主机。例如百度服务器的IP地址是202.108.22.5,但是很有有人能记住,通常只要输入域名“www.baidu.com”就可以。域名和IP地址的转换由如下函数实现:

    struct hostent  * gethostbyname(const char *hostname)

    struct hostent  * gethostbyaddr(const void *addr,int len,inttype)

    在<netdb.h>中定义了structhostent结构体,该结构体实现了主机的域名和IP地址的映射:

    struct hostent

    {

           char  *h_name;                 //主机的正式名称

           char  **h_aliases;                 //主机的别名

           int   h_addrtype;                     //主机的地址类型AF_INET

           int    h_length;                //主机的地址长度        IPv4  32位4字节 /  IPv6  128位16字节

           char  **h_addr_list;              //主机的IP地址列表

    }

    gethostbyname函数可以将域名(如www.baidu.com)转换为一个结构体指针,在这个结构体中存储了域名和IP地址的对应信息。

    gethostbyaddr函数可以将一个socket地址结构体中的IP地址转换为一个结构体指针,在这个结构体中存储了域名和IP地址的对应信息。

    上述两个函数,调用失败返回NULL,并设置h_errno错误变量。

     

     

    1.5 TCP socket编程实例

    (1) 主要API函数

    socket

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    int socket(intdomain, int type,      int protocol);

    【函数功能】

    socket函数用来创建一个用于网络通信的套接字,并返回该套接字的文件描述符(整型)。

    【函数参数】

    -   domain指定通信所使用的协议族类型(如AF_INET, AF_INET6,AF_UNIX等。TCP/IP使用的协议类型为PF_INET或AF_INET) 。

    -   type指定套接字的类型(TCP套接字为SOCK_STREAM,UDP套接字为SOCK_DGRAM)。

    -   protocol指定所使用的协议号。通常该参数的值设为0即可,表示只使用协议族中的其中一种协议。

    【返 回 值】

    成功,返回新建套接字的文件描述符,失败,返回-1。

    ==================================================================

    bind

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    int bind(int   sockfd,  const struct sockaddr  *addr,   socklen_t  addrlen);

    【函数功能】

    bind函数用来为新创建的套接字绑定本地IP地址和协议端口号(IP和协议端口存放在套接字接口结构体中)。

    【函数参数】

    -   sockfd指定要进行绑定操作的套接字的文件描述符。

    -   addr指定套接字接口结构体的地址,该结构体中存放了IP地址和端口号。

    -   addrlen指定套接字接口结构体字节的大小。

    【返 回 值】

    成功,返回0,失败,返回-1。

    ==================================================================

    listen

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    int listen(int sockfd,  int  queuelen);

    【函数功能】

    listen函数将服务器端socket接口设置为被动状态(即监听套接字),用于监听客户端的连接请求。

    【函数参数】

    -   sockfd指定要进行监听操作的套接字的文件描述符。

    -   queuelen指定连接请求队列的大小(应对多请求任务)。

    【返 回 值】

    成功,返回0,失败,返回-1。

    ==================================================================

    accept

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    int accept(int  sockfd,      struct sockaddr  *addr, socklen_t  *addrlen);

    【函数功能】

    accept函数用于从连接请求队列中取走一个连接请求(或请求队列为空时,以阻塞模式等待,知道新的连接请求到达),并为该请求创建一个新的用于通信的套接字,并返回通信套接字的文件描述符。accept只能用于流式套接字。

    【函数参数】

    -   sockfd指定监听套接字的文件描述符。

    -   addr套接字地址结构体指针。调用accept成功后,在该结构体中填入远程机器的IP地址和协议端口号。

    -   addrlen初始值设定为struct sockaddr结构体大小的存放地址,调用accept成功后,在其中填入远程机器socket地址的实际大小。

    【返 回 值】

    成功,返回通信套接字的文件描述符(非负),失败,返回-1。

    ==================================================================

    connect

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    int connect(int  sockfd,   conststructsockaddr  *addr,socklen_t  addrlen);

    【函数功能】

    connect函数允许调用者为先前创建的套接字指明远程终端的地址。如果套接字使用TCP,connect就是用3次握手创建一个连接。

    【函数参数】

    -   sockfd指定要进行连接的远程终端的套接字文件描述符。

    -   addr指定远程终端的IP地址和协议端口号。

    -   addrlen指定struct sockaddr结构体的大小。

    【返 回 值】

    成功,返回0,失败,返回-1。

    ==================================================================

    send

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    ssize_t  send(int  sockfd ,constvoid  *buf , size_t  len ,  int  flags);

    【函数功能】

    send函数用来发送消息到与之建立网络连接的套接字。

    【函数参数】

    -   sockfd指定发送端的套接字文件描述符。

    -   buf指定待发送数据(在本地)的地址。

    -   len指定待发送数据的最大字节。

    -   flags指定一些额外的功能,功能标志位,该位通常为0,此时等价于write函数。

    【返 回 值】

    ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,size_t是无符号整型。

    成功,返回实际发送的数据字节个数,失败,返回-1,并将errno变量设置为相应的值。

    ==================================================================

    write

    【函数概要】

    #include <unistd.h>

    ssize_t  write(int fd ,  const       void  *buf  ,  size_t       count);

    【函数功能】

    write函数用来向已经打开的文件(即获得该文件的描述符)写入数据。

    【函数参数】

    -   fd指定要写入数据的文件描述符。

    -   buf指定待写入数据(在本地)内存中的地址。

    -   count指定期望写入的数据的最大字节。

    【返 回 值】

    ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,size_t是无符号整型。

    成功,返回实际写入的字节个数,失败,返回-1。

    ==================================================================

    recv

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    ssize_t  recv (int  sockfd ,      void  *buf ,  size_t  len ,  int  flags);

    【函数功能】

    recv函数用来接收远程主机通过已经建立的网络连接套接字所发送来的消息。

    【函数参数】

    -   sockfd指定接收端的套接字文件描述符。

    -   buf指定被接收的数据在本地内存中的地址。

    -   len指定待接收数据的最大字节。

    -   flags指定一些额外的功能,功能标志位,该位通常为0,此时等价于read函数。

    【返 回 值】

    ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,size_t是无符号整型。

    成功,返回实际接收到的数据字节个数。失败,返回-1。

    ==================================================================

    read

    【函数概要】

    #include <unistd.h>

    ssize_t  read(int fd ,  void  *buf  ,  size_t count);

    【函数功能】

    read函数用来从已经打开的文件(即获得该文件的描述符)中读取数据。

    【函数参数】

    -   fd指定要读取的文件的文件描述符。

    -   buf指定读取到的数据在本地内存中的存放地址。

    -   count指定期望读取到的数据的最大字节。

    【返 回 值】

    ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,size_t是无符号整型。

    成功,返回实际读取到的字节个数,若已经到文件末尾返回0。失败,返回-1。

    ==================================================================

    close

    【函数概要】

    #include <unistd.h>

    int close(int  fd);

    【函数功能】

    close函数用来关闭一个打开的文件。

    【函数参数】

    -   fd指定要关闭的文件的文件描述符。

    【返 回 值】

    成功,返回0。失败,返回-1。

     

    (2)TCP程序实例分析

    服务端程序server_iter.c

    #include <stdlib.h>

    #include <ctype.h>

    #include <unistd.h>

    #include <stdio.h>

    #include <sys/socket.h>

    #include <sys/un.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <netdb.h>

     

    char      host_name[20];        //存放客户机的主机名

    int         port= 8000;                //指定服务器端进程所使用的端口号

     

    int main(void)

    {

           structsockaddr_in     sin, pin;

           int sock_descriptor, temp_sock_descriptor,address_size;

           int i, len, on=1;

           charbuf[16384];

        /***********************创建套接字***********************/

           sock_descriptor= socket(AF_INET, SOCK_STREAM, 0);

           if(sock_descriptor== -1)

    {      perror("call to socket");      exit(1);   }

           //setsockopt(sock_descriptor,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

           /***********************填充地址结构体***********************/

    bzero(&sin,sizeof(sin));

           sin.sin_family= AF_INET;

           sin.sin_addr.s_addr= INADDR_ANY;

           sin.sin_port= htons(port);

           /***********************套接字和地址结构体进行绑定***********************/

    if (bind(sock_descriptor,(struct sockaddr*)&sin, sizeof(sin)) == -1)

          {      perror("call to bind");        exit(1);   }

    /***********************将套接字设置为监听状态***********************/

           if(listen(sock_descriptor,100) == -1)

    {      perror("call to listen");        exit(1);   }

           printf("Acceptingconnections ...\n");             //打印提示信息

    /***********************进行数据交互***********************/

           while(1)

    {

                  address_size= sizeof(pin);

    /***********************处理连接请求,创建通信套接字***********************/

                  temp_sock_descriptor= accept(sock_descriptor, (struct sockaddr *)&pin, &address_size);

                  if(temp_sock_descriptor== -1)

    {      perror("call to accept");      exit(1);   }

    /***********************借助通信套接字进行数据的接收***********************/

                  if(recv(temp_sock_descriptor,buf, 16384, 0) == -1)

    {      perror("call to recv");         exit(1);   }

                  inet_ntop(AF_INET,&pin.sin_addr, host_name, sizeof(host_name)); //将网络字节序转换为点分格式

                  printf("receivedfrom client(%s): %s\n", host_name, buf);        //打印接受到的信息

        /* for this server example, we just convert thecharacters to upper case: */

                  len= strlen(buf);

                  for(i= 0; i < len; i++)

                         buf[i]= toupper(buf[i]);

        /***********************借助通信套接字进行数据的发送***********************/

                  if(send(temp_sock_descriptor,buf, len+1, 0) == -1)

    {      perror("call to send");        exit(1);   }

        /***********************关闭通信套接字***********************/

    close(temp_sock_descriptor);

                  //sleep(60);

           }

    /***********************关闭监听套接字***********************/

           close(sock_descriptor);

    }

     

    客户端程序client.c

    #include <stdlib.h>

    #include <ctype.h>

    #include <unistd.h>

    #include <string.h>

    #include <stdio.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <netdb.h>

     

    char *host_name ="127.0.0.1";             //指定服务器IP地址,回环测试,使用同一张网卡进行数据的发送和接收

    int port = 8000;                                //指定服务器端口号

     

    int main(int argc, char *argv[])

    {

           charbuf[8192]; 

           intsocket_descriptor;

           structsockaddr_in pin;

           char*str = "A default test string";

           /***********************通过启动参数设置服务器IP和测试字符串***********************/

           if(argc< 2)

    {

                  printf("Usage:client \"Any test string\" \"ip address\"\n");

                  printf("Wewill send a default test string.\n");

           }

    else

    {

                  str= argv[1];

                  if(argc ==3)

                  host_name= argv[2];

           }

           /***********************填充地址结构体***********************/

           bzero(&pin,sizeof(pin));

           pin.sin_family= AF_INET;

           inet_pton(AF_INET,host_name, &pin.sin_addr);

           pin.sin_port= htons(port);

           /***********************创建套接字***********************/

    if((socket_descriptor =socket(AF_INET, SOCK_STREAM, 0)) == -1)

    {      perror("Error opening socket\n");    exit(1);   }

           /***********************连接服务器***********************/

    if(connect(socket_descriptor, (struct sockaddr *)&pin, sizeof(pin)) == -1)

    {      perror("Error connecting to socket\n");   exit(1);   }

          

           printf("Sendingmessage %s to server...\n", str);  //打印提示信息

    /*     if (send(socket_descriptor, str,strlen(str)+1, 0) == -1)

    {      perror("Error in send\n");   exit(1);   }      */

           /***********************向服务器发送数据***********************/

    if(write(socket_descriptor, str, strlen(str)+1) == -1)

    {      perror("Error in send\n");   exit(1);   }

           printf("..sent message.. wait for response...\n");

           //close(socket_descriptor);

           //exit(1);

    /*     if(recv(socket_descriptor, buf, 8192, 0) ==-1)

    {      perror("Error in receiving responsefrom server\n");   exit(1);   }      */

           /***********************读取服务器回传的数据***********************/

    if(read(socket_descriptor,buf, 8192) == -1)

    {      perror("Error in receiving response from server\n");   exit(1);   }

           printf("\nResponsefrom server:\n\n%s\n", buf);

    /***********************关闭通信套接字***********************/    

    close(socket_descriptor);

           return1;

    }

    2、UDP编程模型

    2.1 UDP套接字socket编程步骤

    UDP网络编程整体步骤如图3-2所示。

    (1)服务器端(server)编程步骤

    ① 调用socket创建一个不与任何网络连接相对应的套接口(socket),其返回值是一个文件描述符;

    ② 调用bind,将刚创建的socket与本机的IP地址和端口号绑定,此时该socket为一个3元组;

    (IPv4协议,本地IP,本地端口)

         ③调用recvfrom,从套接口接收客户端发送过来的数据(同时获取客户端的IP地址和端口号),如果此时客户端并未发送过来数据,那么recvfrom将阻塞到数据到达为止;

           ④调用sendto(将客户端的IP地址和端口号作为参数传入),向套接口写入处理后的数据,该数据将通过网络到达客户端;

    ⑤当所有的数据都已经处理完毕,将调用close函数关闭连接套接口,关闭网络连接,结束通信。

     (2)客户机端(client)编程步骤

    ① 调用socket创建一个不与任何网络连接相对应的套接口(socket),其返回值是一个文件描述符;

    ② 调用sendto(将服务器IP和端口号作为参数传入)向socket中写入数据, OS将选择一个本机尚未使用的大于1024的端口号作为本地端口号,并选择本机的IP地址作为本地IP(此时,客户端的socket已经是5元组)该数据到达服务器后,被recvfrom函数读取;

    ③ 调用recvfrom函数,等待从socket读取服务器通过sendto回传的数据;

    ④ 当所有数据接收完毕,调用close函数,关闭网络连接,结束通信。

    图3-2  UDP socket编程模型

     

    2.2 UDP socket编程实例

     (1)主要API函数

    sendto

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    ssize_t  sendto(int  sockfd,  const void * buf ,  size_t  len ,  int  flags ,  const struct sockaddr *dest_addr,

    socklen_t addrlen);

    【函数功能】

    sendto函数用来发送消息到与之建立网络连接的套接字(最后两个参数为NULL和0时,等价于send函数)。

    【函数参数】

    -   sockfd指定发送端的套接字文件描述符。

    -   buf指定待发送数据(在本地内存)的地址。

    -   len指定待发送数据的最大字节。

    -   flags指定一些额外的功能,功能标志位,该位通常为0。

    -   dest_addr指定数据接收端的地址结构体(含IP地址和端口号)的指针。

    -   addrlen数据接收端地址结构体的大小。

    【返 回 值】

    ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,size_t是无符号整型。

    成功,返回实际发送的数据字节个数,失败,返回-1。

    ==================================================================

    recvfrom

    【函数概要】

    #include <sys/types.h>

    #include <sys/socket.h>

    ssize_t  recvfrom(int  sockfd ,  void  *buf ,  size_t  len ,  int flags ,  struct sockaddr  *src_addr ,

                               socklen_t  *addrlen);

    【函数功能】

    recvfrom函数用来接收远程主机通过已经建立的网络连接套接字所发送来的消息。

    【函数参数】

    -   sockfd指定接收端的套接字文件描述符。

    -   buf指定被接收的数据在本地内存中的地址。

    -   len指定待接收数据的最大字节(即缓冲区的最大长度)。

    -   flags指定一些额外的功能,功能标志位,该位通常为0。

    -   src_addr指向的结构体将被对端(即数据发送端)地址(含IP和端口号)所填充。

    -   addrlen所指向的位置,调用前应填入src_addr指向的结构体的大小,调用后将被填入对端地址的实际大小。

    注:若不需要对端的IP地址和端口号,将src_addr和addrlen设置为NULL即可。

    【返 回 值】

    ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int,size_t是无符号整型。

    成功,返回实际接收到的数据字节个数。失败,返回-1。

    (2)UDP程序实例分析

    服务端程序udpserver.c

    #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <stdio.h>

    #include <errno.h>

    #include <stdlib.h>

    #include <strings.h>

    #include <unistd.h>

    #include <string.h>

    #include <ctype.h>

     

    #define SERVER_PORT 8888                     //定义服务器端口号

    #define MAX_MSG_SIZE 1024                    //定义缓冲区大小

     

    void udps_respon(int sockfd)

    {

           structsockaddr_in     addr;

           intn, i;

           unsignedint        addrlen = sizeof(structsockaddr_in);

           charmsg[MAX_MSG_SIZE];

    while(1)

          {     /***********************数据的接收***********************/

                  n= recvfrom(sockfd, msg, MAX_MSG_SIZE, 0, (struct sockaddr*)&addr,&addrlen);

                  msg[n]= 0;

                  /***********************显示收到的信息***********************/

                  fprintf(stdout,"I have received %s", msg);

                  /***********************数据的处理***********************/

                  n= strlen(msg);

                    for(i = 0; i < n; i++)

                            msg[i] =toupper(msg[i]);

                 /***********************数据的发送***********************/

                  sendto(sockfd,msg, n, 0, (struct sockaddr *)&addr, addrlen);

           }

    }

    int main(void)

    {

           intsockfd;

           structsockaddr_in addr;

           /***********************创建套接字***********************/

           sockfd= socket(AF_INET, SOCK_DGRAM, 0);

           if(sockfd < 0)

    {      perror("socket");   exit(1);      }

    /***********************填充地址结构体***********************/

           bzero(&addr,sizeof(struct sockaddr_in));

           addr.sin_family= AF_INET;

           addr.sin_addr.s_addr= htonl(INADDR_ANY);

           addr.sin_port= htons(SERVER_PORT);

    /***********************套接字和地址结构体进行绑定***********************/  

    if (bind(sockfd, (structsockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)

    {     perror("bind");      exit(1); }

           udps_respon(sockfd);      //数据交互

           close(sockfd);                   //关闭通信套接字

    return 0;

    }

     

    客户端程序udpclient.c

    #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <errno.h>

    #include <stdio.h>

    #include <unistd.h>

    #include <string.h>

    #include <stdlib.h>

    #include <arpa/inet.h>

     

    #define MAX_BUF_SIZE 1024              //定义缓冲区大小

     

    void udpc_requ(int sockfd, conststruct sockaddr_in *addr, int len)

    {

           charbuffer[MAX_BUF_SIZE];

           intn;

           while(1)

    {     /***********************数据的发送***********************/

                  fgets(buffer,MAX_BUF_SIZE, stdin);   //从标准输入文件获取数据

                  sendto(sockfd,buffer, strlen(buffer), 0, (struct sockaddr *)addr, len);

                  printf("Ihave sent to server %s", buffer);            

                  printf("Waitingrespond from server\n");

                  /***********************数据的接收***********************/

    bzero(buffer,MAX_BUF_SIZE);

                  n= recvfrom(sockfd, buffer, MAX_BUF_SIZE, 0, NULL, NULL);

                  buffer[n]= 0;

                  printf("Ihave received from server ");

                  fputs(buffer,stdout);                //将回传数据显示到屏幕上(标准输出)

                  printf("\n");

           }

    }

     

    int main(int argc, char **argv)

    {

           intsockfd, port;

           structsockaddr_in addr;

           /***********************通过启动参数设置服务器IP和测试字符串***********************/

           if(argc != 3)

    {      fprintf(stderr, "Usage:%s server_ip server_port\n",argv[0]);    exit(1);   }

           if((port = atoi(argv[2])) < 0)

    {      fprintf(stderr, "Usage:%s server_ip server_port\n",argv[0]);    exit(1);   }

           /***********************创建套接字***********************/

    sockfd = socket(AF_INET,SOCK_DGRAM, 0);

           if(sockfd < 0)

    {     fprintf(stderr, "Socket Error:%s\n", strerror(errno));     exit(1);   }

           /***********************填充地址结构体***********************/

    bzero(&addr,sizeof(struct sockaddr_in));

           addr.sin_family= AF_INET;

           addr.sin_port= htons(port);

           if(inet_aton(argv[1], &addr.sin_addr) < 0)

    {     fprintf(stderr, "Ip error:%s\n", strerror(errno));     exit(1);   }

           udpc_requ(sockfd,&addr, sizeof(struct sockaddr_in));             //数据交互

           close(sockfd);             //关闭通信套接字

           return0;

    }

     

    说明:

    对服务器而言:

    1、没有调用accept,因为不需要建立连接;

    2、一个套接字接口可以接受不同的IP发送来的数据,对端socket地址由recvfrom获得。

    对客户机而言:

    1、没有调用connect,因为不需要建立连接;

    2、首次发送数据时,由操作系统临时选择本地IP地址和端口;由sendto指定目的IP地址和端口号,因此一个套接字接口可以向不同的IP发送数据。

     

     

     

     

     

     

     

     

     

     

     

     

    附录

    按照posix标准,一般整形对应的*_t类型为:

    1字节     uint8_t             2字节    uint16_t           4字节     uint32_t           8字节    uint64_t

    附:C99标准中inttypes.h的内容

    00001 /*

    00002    inttypes.h

    00003 

    00004    Contributors:

    00005      CreatedbyMarek Michalkiewicz <marekm@linux.org.pl>

    00006 

    00007    THISSOFTWAREIS NOT COPYRIGHTED

    00008 

    00009    Thissourcecode is offered for use in the public domain.  You may

    00010    use,modifyor distribute it freely.

    00011 

    00012    Thiscodeis distributed in the hope that it will be useful, but

    00013    WITHOUTANYWARRANTY.  ALLWARRANTIES, EXPRESS OR IMPLIED ARE HEREBY

    00014    DISCLAIMED.  Thisincludes but is not limited towarranties of

    00015    MERCHANTABILITYorFITNESS FOR A PARTICULAR PURPOSE.

    00016  */

    00017 

    00018 #ifndef __INTTYPES_H_

    00019 #define __INTTYPES_H_

    00020 

    00021 /* Use [u]intN_t if youneed exactly N bits.

    00022    XXX-doesn't handle the -mint8 option.  */

    00023 

    00024 typedef signed char int8_t;

    00025 typedef unsigned char uint8_t;

    00026 

    00027 typedef int int16_t;

    00028 typedef unsigned int uint16_t;

    00029 

    00030 typedef long int32_t;

    00031 typedef unsigned long uint32_t;

    00032 

    00033 typedef long long int64_t;

    00034 typedef unsigned long long uint64_t;

    00035 

    00036 typedef int16_t intptr_t;

    00037 typedef uint16_t uintptr_t;

    00038 

    00039 #endif

     

     

    展开全文
  • C语言编程源码之-网络,一步步的学习编程,只有不断的编码,调试,修改自己犯的错误,才能改进自己的习惯,进阶自己的能力
  • C语言的学习,一般的方式是,先学C,然后是C++,最好还要有汇编...其实,具有了C语言基础后,再有一些基本的C++类的概念,就可以直接学习Windows C编程了。一、走近Windows C语言很多语言都把显示一个“Hello,World!...

    C语言的学习,一般的方式是,先学C,然后是C++,最好还要有汇编语言和微机原理基础,然后才是Visual C++。这样的方式,对学习者来说,要花费很多时间和耐力。而在学校教学中,也没有时间深入学习Windows编程的实用技术了。

    其实,具有了C语言基础后,再有一些基本的C++类的概念,就可以直接学习Windows C编程了。

    一、走近Windows C语言

    很多语言都把显示一个“Hello,World!”做为第一个入门程序,C语言的第一个程序是这样的:

    1-131-jpg_6_0_______-572-0-0-572.jpg

    如果把main函数写成带参数的main函数,应该是:

    1-131-jpg_6_0_______-572-0-131-572.jpg

    Windows C的第一个程序和这个程序在形式和原理上都是一致的,只是有两点不同:

    1.主函数接收的形参不只是命令行中的字符串的个数和字符串的首地址。

    2.C语言的很多函数在Windows C中都可以继续使用,但象printf()屏幕显示等函数就不能继续使用了。因为Windows是多任务操作系统,屏幕已不再为某一个应用程序所独有,Windows C应用程序要显示字符串,需要使用Windows提供的API函数,开自己的窗口

    下面是一个最简单的,显示“Hello,World!”的Windows C程序:

    1-155-jpg_6_0_______-572-0-262-572.jpg

    展开全文
  • C语言是面向过程的,而C++是面向对象的 海风教育投诉 海风教育在线辅导0元一对一试听课等你来领取,领取课程方法: 1、私信留下您的手机号和姓名,需要补习的科目。 2、也可以在海风教育官网留下您的手机号领取 ...
  • //这是我写的,自定义打印菱形大小的程序,#includeintmain(){intx,y;inti,j,mid,cha;printf("pleaseenterx");//输入想打印菱形的长度scanf("%d",&x);printf("pleaseenterthechar'...
  • c源程序及与ANSYS衔接(JIEKOU)/Debug/JIEKOU.exec源程序及与ANSYS衔接(JIEKOU)/Debug/JIEKOU.ilkc源程序及与ANSYS衔接(JIEKOU)/Debug/JIEKOU.objc源程序及与ANSYS衔接(JIEKOU)/Debug/JIEKOU.pchc源程序及与ANSYS衔接...
  • C语言编程实例

    2020-02-10 00:11:28
    C语言渔夫打鱼晒问题 C语言希尔排序算法 C语言冒泡排序算法 C语言直接插入排序算法 C语言快速排序算法 C语言选择排序算法 C语言归并排序算法 C语言二分查找算法,折半查找...
  • linux C语言编程入门

    2012-09-06 22:16:37
    linux C语言编程入门教程,包括文件,进程,信息处理,网络编程等。
  • 网络环境下的C语言编程技巧及实例,偏向于网络方面
  • c语言编程--网络编程

    千次阅读 2014-09-10 09:17:35
    阻塞型的网络编程接口  几乎所有的程序员第一次接触到的网络编程都是从 listen()、send()、recv() 等接口开始的。使用这些接口可以很方便的构建服务器/客户机的模型。  我们假设希望建立一个简单的服务器程序,...
  • 嵌入式C语言编程规范

    2016-06-16 10:13:26
    纵观历史的长河,软件是人类历史上复杂度最高的工业产品,没有之一... 嵌入式C语言编程规范”。 本规范适用于公司内使用C语言编码的所有嵌入式软件。本规范自发布之日起生效,以后新编写的和修改的代码应遵守本规范。
  • Linux嵌入式C语言编程

    2009-12-25 17:00:09
    介绍Linux环境下的C语言编程。尤其是对于进程通信,多线程,socket网络编程等有较为详细的介绍。
  • 搜集网络上的C语言编程资料。 涵盖C语言基础编程的大部分知识,包括基础语法,算法与数据结构,图形编程,系统编程,游戏编程,密码学(加密与解密)等等,还有计算机二级上机和笔试100套。 希望能帮助到像我一样...
  • IO模型在Richard Stevens的《UNIX网络编程,第一卷》(程序猿必备!)一书中有非常详尽的描述,以下简要介绍,并给出代码示例。 另外比较好的总结性blog,推荐: 使用异步 I/O 大大提高应用程序的性能 IO - ...
  • C语言编程指导

    2012-11-04 21:32:01
    Socket 编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等等… 好在我已经将这些事完成了,我将和所有人共享...
  • linux操作系统下c语言编程入门--网络编程 Linux系统的一个主要特点是他的网络功能非常强大。随着网络的日益普及,基于网络的 应用也将越来越多。 在这个网络时代,掌握了Linux的网络编程技术,将令每一个人处 ...
  • c语言 网络编程示例

    2009-06-06 21:06:53
    网络编程示例 c语言编程的 主机和客户机通信的
  • C语言编程竞赛系统

    2011-12-06 12:27:59
    C语言嵌入式系统编程修炼之背景篇 作者:宋宝华出处:天极责任编辑: 方舟
  • c network C语言网络编程
  • 在一个网络中,我们称服务器S是关键服务器,如果至少有另两部不同的服务器A和B,而A与B之间是所有联络都通过S。即若S崩溃,则A与B之间不能进行通讯。如果一个网络不包含关键服务器,则称它是安全的。...
  • SPWM/cortexm3_macro.lstSPWM/cortexm3_macro.sSPWM/JLinkArm_Target 1.iniSPWM/main.cSPWM/output/cortexm3_macro.oSPWM/output/ExtDll.iexSPWM/output/main.crfSPWM/output/main.dSPWM/output/main.oSPWM/output/P...
  • linux下c语言编程入门

    2010-06-21 18:06:46
    这是一本学习linux下c语言编程的入门书籍,非常适合于初学者,从非常基本的概念入手,结合程序实例介绍了linux下c语言的基础。本书简介明了的介绍了c语言基础知识、进程介绍、文件操作、时间概念、信号处理、消息...
  • C语言网络编程

    2015-03-31 05:23:32
    C语言网络编程总结(IPv4与TCP)server 和 client对socket的处理server : 端口号码 numero of port client :server的ip地址,以及开放的端口号码socket()->IPv4 +TCPinclude int reuse = 1; setsockopt...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,399
精华内容 1,759
关键字:

c语言编程网

c语言 订阅