二、网络编程基础

1、套接字概述

套接字就是网络编程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间的通信)。在网络中,每一个节点(计算机或路由器)都有一个网络地址,也就是IP地址,两个进程通信时,首先要确定各自所在网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中哪一个进程通信,因此套接口中还需要有其他的信息,也就是端口号(port)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应的关系。所以,使用端口号和网络地址的组合就能唯一确定整个网络中的一个网络进程。

把网络地址和端口号信息放在一个结构体中,也就是套接口地址结构,大多数的套接口函数都需要一个指向套接口地址结构的指针作为参数,并以此来传递地址信息。每个协议族都定义了它自己的套接口地址结构,套接字地址结构都以"sockaddr_” 开头,并以每个协议族名中两个字母作为结尾。

下面是socket所在位置:

可以看到套接口有3种类型:

1)流式套接字(SOCK_STREAM)

流式套接字提供可靠的、面向连接的通信刘,保证数据传输的可靠性和按序收发。TCP通信使用的就是流式套接字。

2)数据包套接字(SOCK_DGRAM)

数据报套接字实现了一种不可靠、无连接的服务。数据通过相互独立的报文进行传输,是无序的,并且不保证可靠的传输。UDP通信使用的就是数据报套接字。

3)原始套接字(SOCK_RAW)

原始套接字允许对底层协议(如IP或ICMP)进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。

2、端口号

这里的端口号是逻辑意义上的端口,一般是指TCP/IP 协议中的端口,端口号的范围为0~65535,比如用于浏览网页服务(HTTP协议)的80端口,用于FTP服务的21端口等。其中, 0 到1023 一般被系统程序所使用。

那么TCP/IP协议中的端口指的是什么呢?举个例子,如果IP地址唯一指定了地球上某个地理位置的一间房子,端口号就是出入这间房子的门,只不过这个房子的门有65536个之多,端口是通过端口号来标记的,端口号是一个16位的整数,范围是从0~65535。

端口号只具有本地意义,即端口号只是为了标识本地计算机上的各个进程。在互联网中不同计算机的相同端口号是没有联系的。16bit 的端口号可允许有64K个端口号,这个数目对一个计算机来说是足够用的。

3、IP地址

1)IP地址的作用

IP地址用来表示网络中的一台主机。准确的说,IP地址是一台主机到一个网络的一个连接,因为现在一个主机中会有多个网卡。

一个IP地址包含两部分:网络号和主机号。其中,网络号和主机号根据子网掩码来区分。简单的说,有了源IP 和目标 IP,数据包就能在不同主机之间传输。

2)IP地址格式转换

IP地址有两种不同格式:十进制点分形式和32位二进制形式。前者是用户熟悉的形式,而后者则是网络传输中IP地址的存储方式。

这里主要介绍IPV4地址转换函数,主要有 inet_addr() 、inet_aton() 、inet_ntoa() 。前两者的功能都是将字符串转换成32位网络字节序二进制值,第三个将32位网络字节序二进制地址转换成点分十进制的字符串。

inet_addr() 函数语法如下:

所需头文件 #include <arpa/inet.h>
函数原型 int  inet_addr(const char *strptr);
参数

strptr :要转换的IP地址字符串

函数返回值

成功:32位二进制IP地址(网络字节序)

出错:-1

inet_aton() 函数语法如下:

所需头文件 #include <arpa/inet.h>
函数原型 int  inet_aton(int family, const char *src , void *drt);
参数

family   AF_INET:IPV4协议

AF_INET6:IPV6协议

src:要转换的IP地址字符串

函数返回值

成功:32位二进制IP地址(网络字节序)

出错:-1

inet_ntoa() 函数语法如下:

所需头文件 #include <arpa/inet.h>
函数原型 int  inet_ntoa(int family, const char *src , void *dst, size_t len);
参数

family   AF_INET:IPV4协议

AF_INET6:IPV6协议

src:要转换的二进制IP地址;
dst:存放十进制地址字符串的缓冲区;
len:缓冲区的长度

函数返回值

成功:返回dst

出错:NULL

4、字节序

字节序又称为主机字节序 Host Byte Order,HBO,是指计算机中多字节整型数据的存储方式。字节序有两种:大端(高位字节存储在低位地址,低位字节存储在高位地址)和小端(和大端序相反,PC通常采用小端模式)。

为什么需要字节序?在网络通信中,发送方和接收方有可能使用不同的字节序;

为了保证数据接受后能被正确的解析处理,统一规定:数据以高位字节优先顺序在网络上传输。因此数据在发送前和接收后都需要在主机字节序和网络字节序之间转换。

1)函数说明

字节序转换涉及4个函数:htons() 、ntohs() 、htonl() 和 ntohl() 。这里的 h 代表 host , n 代表 network , s 代表 short , l 代表 long 。通常 16bit 的IP端口号用前两个函数处理,而 IP 地址用后两个函数来转换。调用这些函数只是使其得到相应的字节序,用户不需要知道该系统的主机字节序和网络字节序是否真的相等。如果两个相同不需要转换的话,该系统的这些函数会定义成空宏。

2)函数格式

所需头文件 #include <netinet/in.h>
函数原型 unit16_t htons(unit 16_t hostshort);
unit32_t htonl(unit 32_t hostlong);
unit16_t ntohs(unit 16_t netshort);
unit32_t ntohl(unit 32_t netlong);
函数传入值 hostshort:主机字节序的16bit 数据
hostlong :主机字节序的32t 数据
netshort  :网络字节序的16bit 数据
netlong:网络字节序的32bitt 数据
函数返回值 成功:返回转换字节序后的数值
出错:-1

5、TCP编程

函数说明

socket()编程的基本函数有socket() 、bind()、listen()、accept()、send()、sendto()、recv()以及recvfrom()等。下面先简单介绍上述函数的功能,再结合流程图具体说明

1)socket() :该函数用于创建一个套接字,同时指定协议和类型。

2)bind()     :该函数将保存在相应地址结构中的地址信息与套接字进行绑定。它主要用于服务器端,客户端创建的套接字可以不绑定地址。

3)listen()   :在服务端程序成功建立套接字并与地址进行绑定以后,通过调用listen() 函数将TCP连接后,该函数会返回一个新的已连接套接字。

5)connect():客户端通过该函数向服务器端的监听套接字发送连接请求。

6)send() 和 recv():这两个函数通常在TCP通信过程中用于发送和接收数据,也可用于UDP中。

7)sendto()和recvfrom() :这两个函数一般在UDP通信过程中用于发送和接受数据。当用于TCP时,后面的几个与地址有关的参数不起作用,函数作用等同于 send() 和 recv()

服务器端和客户端使用TCP的流程如下:

可以看到通信工作的大致流程如下:

1)服务器先用socket() 函数来建立一个套接口,用这个套接口完成通信的监听及数据的收发;

2)服务器用bind() 函数来绑定一个端口号和IP地址,使套接口与制定的端口号和IP地址相关联;

3)服务器调用listen()函数,使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。

4)客户机调用socket()函数建立一个套接口,设定远程IP和端口。

5)客户机调用 connect() 函数链接远程计算机指定的端口。

6)服务器调用 accept() 函数来接受远程计算机的连接请求,建立起与客户机之间的通信连接。

7)建立连接以后,客户机用write() 函数 (或send()函数)向socket() 中写入数据,也可以用 read() 函数(或recv()函数)读取服务器发送来的数据。

8)服务器用 read()函数(或recv()函数)读取客户机发送来的数据,也可以用 write() 函数(或send()函数)来发送数据。

9)完成通信以后,使用close()函数关闭socket 连接。

函数格式:

1)创建套接口 socket() 函数

其语法要点

所需头文件 #include <sys/socket.h>
函数原型  int socket(int family, int type,int protocol);
函数传入值 family:协议族
type:套接字类型
protocol:0(原始套接字除外)
函数返回值 成功:非负套接字描述符
出错:-1

参数family 指明协议族,取值如:

AF_INET:IPv4协议

AF_INET6:IPv6协议

AF_LOCAL:UNIX域协议

AF_ROUTE:路由套接字

AF_KEY:密钥套接字

这里“AF”代表“Adress Family”(地址族)

types指明通信字节流类型,其取值如:

SOCK_STREAM:流式套接字(TCP方式)

SOCK_DGRAM:数据包套接字(UDP方式)

SOCK_RAM:原始套接字

2)绑定端口 bind()函数

用socket() 函数创建一个套接口后,需要使用bind 函数在这个套接口上绑定一个指定的端口号和IP地址。bind函数原型如下:

所需头文件 #include <sys/socket.h>
函数原型  int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
函数传入值 sockfd:套接字描述符
my_addr:绑定的地址
addrlen:地址长度
函数返回值 成功:0
出错:-1

这里my_addr是IPv4地址,IPv4 套接口地址数据结构以socketaddr_in 命名,定义在 <netinet/in.h>头文件中,形式如下:

[cpp] view plaincopy
  1. struct sockaddr_in
  2. sa_family_t    sin_family; /* address family: AF_INET */
  3. in_port_t      sin_port;   /* port in network byte order */
  4. struct in_addr sin_addr;   /* internet address */
  5. ;

sin_famliy 为套接字结构协议族,如IPv4为AF_INET;

sin_port  是16位 TCP或UDP端口号,网络字节顺序;

结构体成员in_addr也是一个结构体,定义如下:

[cpp] view plaincopy
  1. struct in_addr
  2. {
  3. uint32_t s_addr;     /* address in network byte order */
  4. };

这里s_addr为32位IPv地址,网络字节顺序; 本地地址可以用INADDR_ANY;

字节排序函数上面已经介绍,下面看一个实例:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. #include <unistd.h>
  9. #define PORT 2345
  10. int main()
  11. {
  12. int sockfd;
  13. struct sockaddr_in addr;
  14. int addr_len = sizeof(struct sockaddr_in);
  15. if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
  16. {
  17. perror("socket fail");
  18. exit(-1);
  19. }
  20. else
  21. {
  22. printf("socket created successfully!\nsocket id is %d\n",sockfd);
  23. }
  24. memset(&addr,0,addr_len);
  25. addr.sin_family = AF_INET;
  26. addr.sin_port = htons(PORT);
  27. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  28. if(bind(sockfd,(struct sockaddr *)(&addr),addr_len) < 0)
  29. {
  30. perror("bind error");
  31. exit(-1);
  32. }
  33. else
  34. {
  35. printf("bind port successfully!\nlocal port:%d\n",PORT);
  36. }
  37. return 0;
  38. }

执行结果如下:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/net$ ./socket
  2. socket created successfully!
  3. socket id is 3
  4. bind port successfully!
  5. local port:2345
  6. fs@ubuntu:~/qiang/net$

3)等待监听函数

所谓监听,指的是socket 的端口一直处于等待的状态,监听网络中的所有客户机,耐心等待某一客户机发送请求。如果客户端有连接请求,端口就会接受这个连接。listen 函数用于实现服务器的监听等待功能,它的函数原型如下:

所需头文件 #include <sys/socket.h>
函数原型  int listen(int sockfd, int backlog);
函数传入值 sockfd:套接字描述符
backlog:请求队列中允许的最大请求数,大多数系统默认值为5
函数返回值 成功:0
出错:-1

需要注意的是listen 并未真正的接受连接,只是设置socket 的状态为监听模式,真正接受客户端连接的是accept 函数 。通常情况下,listen 函数会在 socket ,bind 函数之后调用,然后才会调用 accept 函数。

listen函数只适用于SOCK_STREAM或SOCK_SEQPACKET 的socket 类型。如果socket 为 AF_INET ,则参数 backlog 最大值可设至128,即最多可以同时接受128个客户端的请求。

4)接受连接函数

服务器处于监听状态时,如果模式可获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时,再处理客户机的连接请求,接受连接请求的函数时accept,函数原型如下:

所需头文件 #include <sys/socket.h>
函数原型  int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数传入值

sockfd:套接字描述符

my_addr:用于保存客户端的地址,入参

addrlen:地址长度

函数返回值

成功:建立好连接的套接字描述符

出错:-1

当 accept 函数接受一个连接时,会返回一个新的 socket 标识符,以后的数据传输与读取就是通过这个新的socket 编号来处理,原来参数中的 socket 也可以继续使用。接受连接以后,远程主机的地址和端口信息会保存在 addr 所指的结构体内。

下面是个实例,体验listen 、accept函数的使用:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <unistd.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #define PORT 2345
  10. int main()
  11. {
  12. int sockfd,newsockfd;
  13. struct sockaddr_in addr,caddr;
  14. int addr_len = sizeof(struct sockaddr_in);
  15. int caddr_len = sizeof(struct sockaddr_in);
  16. if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
  17. {
  18. perror("socket error");
  19. exit(-1);
  20. }
  21. else
  22. {
  23. printf("socket successfully!\n");
  24. printf("socket id : %d\n",sockfd);
  25. }
  26. memset(&addr,0,addr_len);
  27. addr.sin_family = AF_INET;
  28. addr.sin_port = htons(PORT);
  29. addr.sin_addr.s_addr = htonl(INADDR_ANY);
  30. if(bind(sockfd,(struct sockaddr *)&addr,addr_len) == -1)
  31. {
  32. perror("bind error");
  33. exit(-1);
  34. }
  35. else
  36. {
  37. printf("bind successfully!\n");
  38. printf("local port : %d\n",PORT);
  39. }
  40. if(listen(sockfd,5) == -1)
  41. {
  42. perror("listen error");
  43. exit(-1);
  44. }
  45. else
  46. {
  47. printf("listening...\n");
  48. }
  49. if((newsockfd = accept(sockfd,(struct sockaddr *)&caddr,&caddr_len)) == -1)
  50. {
  51. perror("accept error");
  52. exit(-1);
  53. }
  54. else
  55. {
  56. printf("accepted a new connection ..\n");
  57. printf("new socket id : %d\n",newsockfd);
  58. }
  59. return 0;
  60. }

执行程序,得到输出结果:

[cpp] view plaincopy
  1. fs@ubuntu:~/qiang/netprogram/tmp$ ./lisacc
  2. socket successfully!
  3. socket id : 3
  4. bind successfully!
  5. local port : 2345
  6. listening...

程序运行到这停止,并一直在这里等待,说明本机计算机的 2345号端口正处于监听的状态,等待本机上的连接服务请求。此时打开浏览器,在浏览器地址栏中输入下列形式的地址:

[cpp] view plaincopy
  1. http://192.168.3.51:2345/

这个地址是笔者个人IP地址,按"ENTER" 键,这样浏览器会请求连接本地计算机上的2345号端口。此时终端中显示如下结果:

[cpp] view plaincopy
  1. accepted a new connection ..
  2. new socket id : 4

表明程序已经接受了这个连接,并创建了一个新的套接口(ID为4),然后退出了程序。

5、请求连接函数

所谓请求连接,是指在客户机向服务器发送信息之前,需要先发送一个连接请求,请求与服务器建立TCP通信连接。connect 函数可以完成这项功能,函数原型如下:

所需头文件 #include <sys/socket.h>
函数原型  int connect(int sockfd, struct sockaddr * serv_addr, int addrlen);
函数传入值

sockfd:套接字描述符

sock_addr:服务器端地址

addrlen:地址长度

函数返回值

成功:0

出错:-1

这里ser_addr 是一个结构体指针,指向一个sockaddr 结构体,这个结构体存储着远处服务器的IP与端口号信息。

6、数据读写函数

TCP/UDP读写函数总结,注意函数要成对使用

1)send函数

建立套接口并完成通信连接以后,可以把信息传送到远程主机上,这个过程就是信息的发送。而对于远程主机发送来的信息,本地主机需要进行接收处理。下面开始讲述这种面向连接的套接口信息发送与接收操作。

用connect 函数连接到远程计算机以后,可以用 send 函数将应答信息发送给请求服务的本地主机,通信时双向的,并且通信的双方是对等的。

send() 函数原型如下:

所需头文件 #include <sys/socket.h>
函数原型  int send(int sockfd, const void*buf, int len, int flags);
函数传入值

sockfd:套接字描述符

buf:发送缓冲区的地址 入参

len:发送数据的长度

flags:一般为0

函数返回值

成功:实际发送的字节数

出错:-1

2)recv()函数

函数recv 可以接收远程主机发送来的数据,并将这些数据保存到一个数组中,函数原型如下:

所需头文件 #include <sys/socket.h>
函数原型  int recv(int sockfd, const void*buf, int len, int flags);
函数传入值

sockfd:套接字描述符

buf:存放接收数据的缓冲区 出参

len:接收数据的长度

flags:一般为0

函数返回值

成功:实际接收的字节数

出错:-1

Linux 系统应用编程——网络编程(socket编程)相关推荐

  1. Linux系统下计算机C语言的编程技巧

    C语言在多程序编辑中发挥着基础性作用,并在国际范围得到了全面应用.科技的蓬勃发展,使得人们更加重视C语言技术,并对C语言提出更多的要求.但Linux系统是最主要的操作系统之一,基于此开展C语言编程工作 ...

  2. socket recv 服务端阻塞 python_网络编程(基于socket编程)

    网络编程(基于socket编程) socket套接字:应用程序通常通过socket"套接字"向网络发送请求或应答网络请求,是主机间或同一计算机中的进程间相互通讯 socket是介于 ...

  3. Linux系统下网卡网络配置基础

    Ifconfig命令使LINUX核心知道软件回送和网卡这样一些网络接口,这样Linux就可以使用它们.除了上面介绍的这些用法之外,ifconfig命令用来监控和改变网络接口的状态,并且还可以带上很多命 ...

  4. linux系统下ntp网络时钟服务器(NTP服务器)的搭建和使用

    linux系统下ntp网络时钟服务器(NTP服务器)的搭建和使用 linux系统下ntp网络时钟服务器(NTP服务器)的搭建和使用 安徽京准科技开发的NTP网络时间源设备 参考 ahjzsz.com  ...

  5. linux系统下重启网络服务的两种方法

    linux系统下重启网络服务的两种方法 发布时间:2020-04-02 11:25:25 来源:亿速云 阅读:207 作者:小新 今天小编给大家分享的是linux系统下重启网络服务的两种方法,很多人都 ...

  6. Linux系统搭建NFS网络共享存储

    Linux系统搭建NFS网络共享存储 一.NFS概述: NFS是一种基于TCP/IP传输的网络文件系统协议,最初由SUN公司开发.通过NFS协议,客户机可以像访问本地目录一样访问远程服务器中的共享资源 ...

  7. Linux 系统时间与网络时间不一致 时间同步

    Linux 系统时间与网络时间不一致 时间同步 //查看时间date Tue Feb 25 20:15:18 CST 2020 //修改时间 date -s "20200225 20:16: ...

  8. 11单元-Linux系统下的网络配置

    Linux系统下的网络配置 1.IP ADDRESS -- internet protocol ADDRESS (网络进程地址) ip地址:网络位 + 主机位 ipv4 -- internet pro ...

  9. linux两块网卡不通,由安装两块网卡的linux系统中引起网络不通想到的

    由安装两块网卡的linux系统中引起网络不通想到的 由安装两块网卡的linux系统中引起网络不通想到的 一天,小王突然急匆匆的来找我,他说:"我在机子上刚装的redhat怎么老也ping不通 ...

  10. Linux操作系统下C语言网络编程(全文23475字,包含了Linux系统下所有网络编程的知识点,附程序代码)

    一.简介 如今网络应用随处可见,web.http.email 等这些都是网络应用程序,他们都有着基于相同的基本编程模型,有着相似的整体逻辑结构,并且还有着相同的编程接口.我们需要了解基本的客户端-服务 ...

最新文章

  1. 美国国防部黑客大比武 “白帽黑客”受邀请
  2. servlet获取相对路径 绝对路径
  3. 映像劫持技术(2):实例
  4. 秘密潜入2小辣椒_短暂潜入2次,我显然不知道自己作为开发人员正在做什么
  5. 【软件工程】极限编程
  6. 经典面试题(16):以下代码将输出的结果是什么?
  7. 后ERP时代Oracle EBS的机遇与挑战,云和奥创沉心钻研十年谈
  8. 运行Android项目时,报Installation failed due to invalid APK file!错误的解决办法
  9. Mac新手需掌握的操作技巧——屏幕篇
  10. linux后门rootkit程序介绍
  11. 微信小程序实现表格展示
  12. 安全帽佩戴检测算法研究
  13. 基于WEB 的实时事件通知方案
  14. linux中使用jmeter压测
  15. 力扣 1414. 和为 K 的最少斐波那契数字数目
  16. 十二要素应用宣言(The Twelve-Factor App)
  17. js新框架 svelte
  18. 解压缩:解压之后中文文件名乱码
  19. Mac修改hosts 设置虚拟域名
  20. IMD/IMT/IME/OMD/OMF等工艺了解

热门文章

  1. 解开 Windows 下的临界区中的代码死锁(转)
  2. 2009年上半年网络工程师考试下午试卷参考答案(二)
  3. ADSL路由器的设置
  4. kfc流程管理炸薯条几秒_炸薯条成为数据科学的最后前沿
  5. Keras框架:Alexnet网络代码实现
  6. 在PHP服务器上使用JavaScript进行缓慢的Loris攻击[及其预防措施!]
  7. 从头学习计算机网络_如何从头开始构建三层神经网络
  8. python:当文件中出现特定字符串时执行robot用例
  9. Nexus-配置vPC 实验三
  10. Centos下Nodejs+npm环境-部署记录