Linux网络编程

来源:互联网 发布:淘宝阿里旅行人工电话 编辑:程序博客网 时间:2022/10/05 06:16

TCP/IP协议概述

1.1 OSI参考模型与TCP/IP参考模型

图1 OSI参考模型与TCP/IP参考模型对应关系

1.2 TCP/IP协议族

TCP/IP 实际上一个一起工作的通信家族,为网际数据通信提供通路。为讨论方便可将

TCP/IP 协议组大体上分为三部分:

1.Internet 协议(IP)

2.传输控制协议(TCP)和用户数据报文协议(UDP)

3.处于TCP 和UDP 之上的一组协议专门开发的应用程序。它们包括:TELNET,文件传送协议(FTP),域名服务(DNS)和简单的邮件传送程序(SMTP)等许多协议。

1.2.1 网络层

第一部分也称为网络层。包括Internet 协议(IP)、网际控制报文协议(ICMP)和地址识别协议(ARP).

Internet 协议(IP)。

该协议被设计成互联分组交换通信网,以形成一个网际通信环境。它负责在源主机和目的地主机之间传输来自其较高层软件的称为数据报文的数据块,它在源和目的地之间提供非连接型传递服务。

网际控制报文协议(ICMP)。

它实际上不是IP层部分,但直接同IP层一起工作,报告网络上的某些出错情况。允许网际路由器传输差错信息或测试报文。

地址识别协议(ARP)。

ARP 实际上不是网络层部分,它处于IP和数据链路层之间,它是在32位IP地址和48位局域网物理地址之间执行翻译的协议。

1.2.2 传输层协议

第二部分是传输层协议。包括传输控制协议和用户数据报文协议。

传输控制协议(TCP)。

由于IP 提供非连接型传递服务,因此TCP应为应用程序存取网络创造了条件,使用可靠的面向连接的传输层服务。该协议为建立网际上用户进程之间的对话负责。此外,还确保两个以上进程之间的可靠通信。它所提供的功能如下。

1.监听输入对话建立请求。

2.请求另一网络站点对话。

3.可靠的发送和接收数据。

4.适度的关闭对话。

1.2.3 应用程序部分

用户数据报文协议(UDP)。UDP 提供不可靠的非连接型传输层服务,它允许在源和目的地站点之间传送数据,而不必在传送数据之前建立对话。此外,该协议还不使用TCP使用的端对端差错校验。当使用UDP时,传输层功能全都发挥,而开销却比较低。它主要用于那些不要求TCP协议的非连接型的应用程序。例如,名字服务、网络管理、视频点播和网络会议等。

最后是应用程序部分。这部分包括Telnet,文件传送协议(FTP 和TFTP),简单的文件传送协议(SMTP)和域名服务(DNS)等协议。

TCP/IP 使用了主干网络,能连接各种主机和LAN 的多级分层结构,局部用户能方便的联网,不致影响到整个网络系统。此外这种结构还有利于局部用户控制操作和管理。

TCP/IP 具有两个主要功能。第一是IP在网络之间(有时在个别网络内部)提供路由选择。第二是TCP将TP传递的数据传送的接收主机那的适当的处理部件。

1.3 Internet 协议(IP)

IP主要有以下四个主要功能:

(1)数据传送

(2)寻址

(3)路由选择

(4)数据报文的分段

1.3.1 IP功能

Ip的主要目的是为数据输入/输出网络提供基本算法,为高层协议提供无连接的传送服务。这意味着在IP将数据递交给接收站点以前不在传输站点和接收站点之间建立对话(虚拟链路)。它只是封装和传递数据,但不向发送者或接收者报告包的状态,不处理所遇到的故障。

IP协议不注意包内的数据类型,它所知道的一切是必须将某些称为IP 帧头的控制协议加到高层协议(TCP 或者UDP)所接受的数据上。

图3 封装在Ethernet 帧中的IP 头

1.3.2 IP 地址

在TCP/IP网络中,每个主机都有唯一的地址,它是通过IP协议来实现的。IP协议要求在每次与TCP/IP网络建立连接时,每台主机都必须为这个连接分配一个唯一的32位地址,因为在这个32位IP地址中,不但可以用来识别某一台主机,而且还隐含着网际间的路径信息。需要强调指出的,这里的主机是指网络上的一个节点,不能简单地理解为一台计算机,实际上IP地址是分配给计算机的网络适配器(即网卡)的,一台计算机可以有多个网络适配器,就可以有多个IP地址,一个网络适配器就是一个节点。

Ip地址为32位地址,一般以4个字节表示。每个字节的数字又用十进制表示,即每个字节的数的范围是0~255,且每个数字之间用点隔开,例如:192.168.0.112,这种记录方法称为“点-分”十进制记号法。IP地址的结构如下所示:

1.3.3 IP地址的分类

Internet地址可分成5类:

 

A、B、C三类由InterNIC(Internet网络信息中心)在全球范围内统一分配,D、E类为特殊地址。

 

 

A 类网络地址有128 个(支持127)个网络,占有最左边的一个字节(8 位)。高位(0)表示识别这种地址的类型。

B 类地址使用左边两个8 位用来网络寻址。两个高位(10)用于识别这种地址的类型,其余的14 位用作网络地址,右边的两个字节(16 位)用作网络节点。

C 类地址是最常见的Internet 地址。三个高位(110)用于地址类型识别,左边三个字节的其余21 位用于寻址。C 类地址支持1046个网络,每个网络可多达256 端点。

D 类地址是相当新的。它的识别头是1110,用于组播,例如用于路由器修改。

E 类地址为时延保留,其识别头是11110。

1.4 传输控制协议(TCP)

TCP(传输控制协议Transmission Control Protocol)是重要的传输层协议,传输层软件TCP的目的是允许数据同网络上的另外站点进行可靠的交换。它能提供端口编号的译码,以识别主机的应用程序,而且完成数据的可靠传输。

TCP 协议具有严格的内装差错检验算法确保数据的完整性。

TCP 是面向字节的顺序协议,这意味着包内的每个字节被分配一个顺序编号,并分配给每包一个顺序编号。

图4 TCP 头信息

1.5 用户数据报文协议

UDP(用户数据报协议User Datagram Protocol)也是TCP/IP 的传输层协议,它是无连接的,不可靠的传输服务。当接收数据时它不向发送方提供确认信息,它不提供输入包的顺序,如果出现丢失包或重份包的情况,也不会向发送方发出差错报文。

UDP 的主要作用是分配和管理端口编号,以正确无误的识别运行在网络站点上的个别应用程序。由于它执行功能时具有较低的开销,因而执行速度比TCP快。它多半用于不需要可靠传输的应用程序,例如网络视频点播和视频会议等。

图5 UDP 头信息

1.6 TCP/IP 协议分组服务

控制数据的协议

TCP以连接为基础,即两台电脑必须先建立一个连接,然后才能传输数据。事实上,发送和接受的电脑必须一直互相通讯和联系。

UDP是一个无连接服务,数据可以直接发送而不必在两台电脑之间建立一个网络连接。它和有连接的TCP相比,占用带宽少,但是无法确认数据是否真正到达了客户端,而客户端收到的数据也不知道是否还是原来的发送顺序。

数据路由协议

路由协议分析数据包的地址并且决定传输数据到目的电脑最佳路线。他们也可以把大的数据分成几部分,并且在目的地再把他们组合起来。IP处理实际上传输数据。

ICMP(网络控制信息协议Internet Control Message Protocol)处理IP的状态信息,比如能影响路由决策的数据错误或改变。

RIP(路由信息协议Routing Information Protocol)它是几个决定信息传输的最佳路由路线协议中的一个。

OSPF(Open Shortest Path First)一个用来决定路由的协议。

ARP(地址解析协议Address Resolution Protocol)确定网络上一台电脑的数字地址。

DNS(域名系统Domain Name System)从机器的名字确定一个机器的数字地址。

RARP(反向地址解析协议Reverse Address Resolution Protocol)确定网络上一台计算机的地址,和ARP正好相反。

用户服务

BOOTP(启动协议Boot Protocol) 由网络服务器上取得启动信息,然后将本地的网络计算机启动。

FTP(文件传输协议File Transfer Protocol)通过国际互连网从一台计算机上传输一个或多个文件到另外一台计算机。

TELNET(远程登陆)允许一个远程登陆,使用者可以从网络上的一台机器通过TELNET连线到另一台机器,就像使用者直接在本地操作一样。

EGP(外部网关协议Exterior Gateway Protocol)为外部网络传输路由信息。

GGP(网关到网关协议Gateway-to-Gateway Protocol)在网关和网关之间传输路由协议。

IGP(内部网关协议Interior Gateway Protocol)在内部网络传输路由信息

其他协议(也为网络提供了重要的服务)

NFS(网络文件系统Network File System)允许将一台机器的目录被另一台机器上的用户安装(Mount)到自己的机器上,就像是对本地文件系统进行操作一样进行各式各样的操作。

NIS(网络信息服务Network Information Service)对整个网络用户的用户名、密码进行统一管理,简化在NIS 服务下整个网络登陆的用户名/密码检查。

RPC(远程过程调用Remote Procedure Call)通过它可以允许远程的应用程序通过简单的、有效的手段联系本地的应用程序,反之也是。

SMTP(简单邮件传输协议Simple Mail Transfer Protocol)一个专门为电子邮件在多台机器中传输的协议,平时发邮件的SMTP 服务器提供的必然服务。

SNMP(简单网络管理协议Simple Network Management Protocol)这是一项为超级用户准备的服务,超级用户可以通过它来进行简单的网络管理。

网络编程基础

2.1 socket概述

linux中的网络编程通过socket接口实现。Socket既是一种特殊的IO,它也是一种文件描述符。一个完整的Socket 都有一个相关描述{协议,本地地址,本地端口,远程地址,远程端口};每一个Socket 有一个本地的唯一Socket 号,由操作系统分配

套接字有三种类型:

流式套接字(SOCK_STREAM)

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

数据报套接字(SOCK_DGRAM)

数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。

原始套接字。

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

2.2 套接字地址结构

struct sockaddr {

unsigned short sa_family; /* address族, AF_xxx */

char sa_data[14]; /* 14 bytes的协议地址 */

};

sa_family 一般来说, IPV4使用“AF_INET”。

sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的。

struct sockaddr_in {

short int sin_family; /* Internet地址族 */

unsigned short int sin_port; /* 端口号 */

struct in_addr sin_addr; /* Internet地址 */

unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/

};

这两个数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便

2.3 字节序列转换

因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,底位在后,而有的系统是底位在前,高位在后 ),而网络传输的数据大家是一定要统一顺序的。所以对与内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换。

下面给出套接字字节转换程序的列表:

htons()——“Host to Network Short” 主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)

htonl()——“Host to Network Long” 主机字节顺序转换为网络字节顺序(对无符号长型进行操作4bytes)

ntohs()——“Network to Host Short” 网络字节顺序转换为主机字节顺序(对无符号短型进行操作2bytes)

ntohl()——“Network to Host Long ” 网络字节顺序转换为主机字节顺序(对无符号长型进行操作4bytes)

2.4 地址格式转换

Linux提供将点分格式的地址转于长整型数之间的转换函数。如: inet_addr()能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。

inet_ntoa()(“ntoa”代表“Network to ASCII”);

包括:inet_aton, inet_ntoa, inet_addr等。

2.5基本套接字调用

socket() bind() connect()

listen() accept() send()

recv() sendto() shutdown()

recvfrom() close() getsockopt() setsockopt() getpeername()

getsockname() gethostbyname()

gethostbyaddr() getprotobyname()

fcntl()

图 6 基于数据流的socket编程流程

 

图7 基于数据报的编程流程

Socket基础编程见:server.c, client.c

cat client.c

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <netdb.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#define SERVPORT 3333

#define MAXDATASIZE 100

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

int sockfd,sendbytes;

char buf[MAXDATASIZE];

struct hostent *host;

struct sockaddr_in serv_addr;

if(argc < 2){

fprintf(stderr,"Please enter the server's hostname!/n");

exit(1);

}

if((host=gethostbyname(argv[1]))==NULL){

perror("gethostbyname");

exit(1);

}

if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){

perror("socket");

exit(1);

}

serv_addr.sin_family=AF_INET;

serv_addr.sin_port=htons(SERVPORT);

serv_addr.sin_addr=*((struct in_addr *)host->h_addr);

bzero(&(serv_addr.sin_zero),8);

if(connect(sockfd,(struct sockaddr *)&serv_addr,/

sizeof(struct sockaddr))==-1){

perror("connect");

exit(1);

}

if((sendbytes=send(sockfd,"hello",5,0))==-1){

perror("send");

exit(1);

}

close(sockfd);

}

cat server.c

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <unistd.h>

#include <netinet/in.h>

#define SERVPORT 3333

#define BACKLOG 10

#define MAX_CONNECTED_NO 10

#define MAXDATASIZE 5

 

int main()

{

struct sockaddr_in server_sockaddr,client_sockaddr;

int sin_size,recvbytes;

int sockfd,client_fd;

char buf[MAXDATASIZE];

if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){

perror("socket");

exit(1);

}

printf("socket success!,sockfd=%d/n",sockfd);

server_sockaddr.sin_family=AF_INET;

server_sockaddr.sin_port=htons(SERVPORT);

server_sockaddr.sin_addr.s_addr=INADDR_ANY;

bzero(&(server_sockaddr.sin_zero),8);

if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){

perror("bind");

exit(1);

}

printf("bind success!/n");

if(listen(sockfd,BACKLOG)==-1){

perror("listen");

exit(1);

}

printf("listening..../n");

if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1){

perror("accept");

exit(1);

}

if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){

perror("recv");

exit(1);

}

printf("received a connection :%s/n",buf);

close(sockfd);

}

 

网络高级编程

由于在前面介绍的函数如connet、recv、send都是阻塞性函数,若资源没有准备好,则调用该函数的进程将进入休眠状态,这样无法实现I/O多路复用了,下面介绍两种I/O多路复用的解决方案。

1、fcntl函数实现(非阻塞方式)

实例见fcntl.c

cat fcntl.c

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/wait.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/un.h>

#include <sys/time.h>

#include <sys/ioctl.h>

#include <unistd.h>

#include <netinet/in.h>

#include <fcntl.h>

#include <unistd.h>

 

#define SERVPORT 3333

#define BACKLOG 10

#define MAX_CONNECTED_NO 10

#define MAXDATASIZE 100

 

int main()

{

struct sockaddr_in server_sockaddr,client_sockaddr;

int sin_size,recvbytes,flags;

int sockfd,client_fd;

char buf[MAXDATASIZE];

if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){

perror("socket");

exit(1);

}

printf("socket success!,sockfd=%d/n",sockfd);

server_sockaddr.sin_family=AF_INET;

server_sockaddr.sin_port=htons(SERVPORT);

server_sockaddr.sin_addr.s_addr=INADDR_ANY;

bzero(&(server_sockaddr.sin_zero),8);

if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){

perror("bind");

exit(1);

}

printf("bind success!/n");

if(listen(sockfd,BACKLOG)==-1){

perror("listen");

exit(1);

}

printf("listening..../n");

if((flags=fcntl( sockfd, F_SETFL, 0))<0)

perror("fcntl F_SETFL");

flags |= O_NONBLOCK;

if(fcntl( sockfd, F_SETFL,flags)<0)

perror("fcntl");

while(1){

sin_size=sizeof(struct sockaddr_in);

if((client_fd=accept(sockfd,(struct sockaddr*)&client_sockaddr,&sin_size))==-1){

perror("accept");

exit(1);

}

if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){

perror("recv");

exit(1);

}

if(read(client_fd,buf,MAXDATASIZE)<0){

perror("read");

exit(1);

}

printf("received a connection :%s",buf);

close(client_fd);

exit(1);

}/*while*/

}

2、select函数实现

实例见select_socket.c

cat select_socket.c

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/wait.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/un.h>

#include <sys/time.h>

#include <sys/ioctl.h>

#include <unistd.h>

#include <netinet/in.h>

#define SERVPORT 3333

#define BACKLOG 10

#define MAX_CONNECTED_NO 10

#define MAXDATASIZE 100

int main()

{

struct sockaddr_in server_sockaddr,client_sockaddr;

int sin_size,recvbytes;

fd_set readfd;

fd_set writefd;

int sockfd,client_fd;

char buf[MAXDATASIZE];

if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){

perror("socket");

exit(1);

}

printf("socket success!,sockfd=%d/n",sockfd);

server_sockaddr.sin_family=AF_INET;

server_sockaddr.sin_port=htons(SERVPORT);

server_sockaddr.sin_addr.s_addr=INADDR_ANY;

bzero(&(server_sockaddr.sin_zero),8);

if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){

perror("bind");

exit(1);

}

printf("bind success!/n");

if(listen(sockfd,BACKLOG)==-1){

perror("listen");

exit(1);

}

printf("listening..../n");

FD_ZERO(&readfd);

FD_SET(sockfd,&readfd);

while(1){

sin_size=sizeof(struct sockaddr_in);

if(select(MAX_CONNECTED_NO,&readfd,NULL,NULL,(struct timeval *)0)>0){

if(FD_ISSET(sockfd,&readfd)>0){

if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1){

perror("accept");

exit(1);

}

if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){

perror("recv");

exit(1);

}

if(read(client_fd,buf,MAXDATASIZE)<0){

perror("read");

exit(1);

}

printf("received a connection :%s",buf);

}/*if*/

close(client_fd);

}/*select*/

}/*while*/

}