linux编程学习 05-01 网络编程

计算机联网的目的

  • 使用远程资源

  • 共享信息,程序和数据

  • 分布处理

协议

  • 计算机网络中实现通信必须有一些约定,如对速率,传输代码,代码结构,传输控制步骤和出错控制步骤等约定,这些约定即被称为通信协议

  • 在两个节点之间要成功进行通信,两个节点之间必须使用相同的“语言”,这些被通信各方共同遵守的约定,语言,规则被称为协议

  • 在internet中,最为通用的网络协议是TCP/IP协议

TCP/IP协议族

  • TCP/IP实际上是一个一起工作的通信家族,为网际数据通信通路

  • TCP/IP协议族大体上分为三个部分

    • internet协议,如IP协议,对应网络层

    • 传输控制协议(TCP)和用户数据报文协议(UDP),对应传输层

    • 处于TCP和UDP之上的一组协议专门开发的应用程序。他们包括:远程登陆协议(TELNET),文件传送协议(FTP),域名服务(DNS)和简单的邮件传送程序(SMTP),超文本传输协议(HTTP)等,对应应用层

网络层协议

  • internet协议(IP)

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

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

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

  • 地址识别协议(ARP)
    -ARP实际上不是网络层部分,他处于IP和数据链路层之间,他是在32位IP地址和48位局域网物理地址之间执行翻译的协议。mac地址与IP地址的转换

传输层协议

  • 传输控制协议(TCP)

    • 可靠的面向连接的传输层服务

    • 主要功能

      • 监听输入对话建立请求

      • 请求另一网络站点对话

      • 可靠的发送和接收数据

      • 适度的关闭对话

  • 用户数据报文协议(UDP)

    • UDP提供不可靠的非连接型传输层服务

      • 他允许在源和目的站点之间传送数据,而不必在传送数据之前建立对话

      • 不使用TCP使用端对端差错校验

      • 传输层功能全部发挥,而开销却比较低

      • 主要用于那些不要求TCP协议的非连接型应用程序。例如,名字服务,网络管理,视频点播和网络会议等

应用层协议

  • TELNET:远程登陆

  • FTP和TFTP:文件传送协议

  • SMTP:简单的文件传送协议

  • DNS:域名服务

  • HTTP:超文本传输协议

Internet协议(IP)

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

  • IP协议主要有以下四个主要功能

    • 数据传送

    • 寻址

    • 路由选择

    • 数据报文的分段

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

IP地址

  • 在TCP/IP网络中,每个主机都有唯一的地址,他是通过IP协议来实现的。

  • IP协议要求在每次与TCP/IP网络建立连接时,每台主机都必须为这个连接分配一个唯一的32位地址,因为在这个32位IP地址,不但可以用来识别某一台主机,而且还隐含着网际间的路径信息

  • 主机是指网络上的一个节点,不能简单的理解为一台计算机,实际上IP地址是分配给计算机的网络适配器(网卡)的,一台计算机可以有多个网络适配器,就可以有多个IP地址,一个网络适配器就是一个节点

  • IP地址为32位地址,一般以4个字节表示。每个字节的数字又用十进制表示,即每个字节的数的范围是0~255,且每个数字之间用“.”隔开,例如:192.168.1.8,这种记录方法称为“点-分”十进制号发。IP地址结构如下
    |网络类型|网络ID|主机ID|

IP地址分类

  • Internet地址可分为5类

地址类型第一个字节的十进制数
A000-127
B128-191
C192-233
D224-239
E240-255
  • A,B,C三类有InterNIC(Internet网络信息中心)在全球范围内同一分配,D,E类为特殊地址

端口号

  • TCP/UDP协议使用16位整数存储端口号,所以每个主机拥有65535个端口

  • 一些端口被IANA分配给指定应用

    • 21:FTP

    • 23:Telnet

    • 80:HTTP

    • RFC 1700(大约有2000个保留端口)

传输控制协议(TCP)

  • TCP(传输控制协议Transmission Control Protocol)是重要的传输层协议,TCP提供一种面向连接的,可靠的字节流服务

  • TCP协议的目的是允许数据同网络上的另外站点进行可靠的交换。他能提供端口编号的译码,以识别主机的应用程序,而且完成数据的可靠传输

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

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

用户数据报文协议(UDP)

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

    • 他允许在源和目的地站点间传送数据,而不必在传送数据之间建立对话

    • 不使用TCP使用的端对端差错校验

    • 传输层功能全部发挥,而开销却比较低

  • 由于他执行功能时具有较低的开销,因此执行速度比TCP快。他多半用于不需要可靠传输的应用程序,例如网络视频点播和视频会议等

TCP和UDP协议区别

  • 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)在内部网络传输路由协议

socket(套接字)

  • socket(套接字)是一种通讯机制,他包含一整套的调用接口和数据结构定义,他给应用程序提供了使用如TCP/UDP等网络协议进行通讯的手段

  • linux中的网络编程通过socket接口实现,socket既是一种特殊的I/O,提供对应的文件描述符。一个完整的socket都有一个相关描述(协议,本地地址,本地端口,远程地址,远程端口),每一个socket有一个本地的唯一socket,由操作系统分配

  • 内核提供的实现网络通信的接口

创建socket

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

  • 返回:成功返回描述符,出错返回-1

  • 头文件

#include <sys/socket.h>
  • socket创建在内核中,若创建成功返回内核文件描述表的socket描述符

  • 参数

    • domain

      • AF_INET IPV4因特网域

      • AF_INET6 IPV6因特网域

      • AF_UNIX unix域

      • AF_UNSPEC 未指定

    • protocol

      • 通常为0,表示按给定的域和套接字类型选择默认协议

    • type

      • SOCK_STREAM

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

      • SOCK_DGRAM

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

      • SOCK_RAW

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

      • SOCK_SEQPACKET

        • 长度固定,有序,可靠的面向链接报文传递

字节序

  • 不同体系结构的主机使用不同字节序存储器中保存多字节整数。字节存储顺序不同,有的系统是高位在前,低位在后,有的系统是低位在前,高位在后

  • 字节序分为大端和小端字节序

  • 网络协议使用网络字节序即大端字节序

字节序转换函数

  • 网络传输的数据大家是一定要同一顺序的,所以对于内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换

    • uint32_t htonl(uint32_t hostlong);

      • 将一个32位整数由主机字节序转换成网络字节序

    • uint12_t htons(uint16_t hostshort);

      • 将一个16位整数由主机字节序转换成网络字节序

    • uint32_t ntohl(uint32_t netlong);

      • 将一个32位整数由网络字节序转换成主机字节序

    • uint16_t ntohs(uint16_t netshort);

      • 将一个16位整数由网络字节序转换成主机字节序

通用地址结构

#include <sys/socket.h>
struct sockaddr
{
        unsigned short sa_family;// Internet 地址族 AF_XXX
        char sa_data[14];// 14bytes的协议地址
};
  • sa_data包含了一些远程电脑的地址,端口和套接字的数目,他里面的数据是杂溶在一起的

  • sa_family一般来说,IPV4使用AF_INET

  • 在传递给需要地址结构的函数时,把指向该结构体的指针转换成(struct sockaddr*)传递进去

因特网地址结构

#include <netinet/in.h>
struct in_addr
{
        in_addr_t s_addr;// ipv4地址
};
struct sockaddr_in
{
        short int sin_family;// internet地址族如AF_INET(主机字节序)
        unsigned short int sin_port;// 端口号,16位值(网络字节序)
        struct in_addr sin_addr;// Internet地址,32位ipv4地址(网络字节序)
        unsigned char sin_zero[8];// 添0(为了格式对齐的填充位)
};
  • 这两个(sockaddr和sockaddr_in)数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便

IPV4地址族和字符地址间的转换

const char* inet_ntop(int domain,const void*restrict addr,char*restrict str,socklen_t size);

  • 返回:成功返回地址字符串指针,出错返回NULL

  • 功能:网络字节序转换成点分十进制
    int inet_pton(int domain,const char*restrict str,void*restrict addr);

  • 返回:成功返回1,无效格式返回0,出错返回-1

  • 功能:点分十进制转换成网络字节序

  • 头文件

#include <arpa/inet.h>
  • 参数

    • domain:internet地址族地址,如AF_INET

    • addr:internet地址,32位IPV4地址(网络字节序)

    • str:地址字符串(点分十进制)指针

    • size:地址字符串大小

填写IPV4地址族结构案例

struct sockaddr_in sin;// 定义一个sockaddr_in结构体
char buf[16];
memset(&sin,0,sizeof(sin));// 内存清0
sin.sin_family = AF_INET;// 填写internet地址族
sin.sin_port = hton((short)3001);// 填写端口号,转换成网络字节序
// 填充sin_addr,需要吧字符串型的ip地址转换成网络字节序
if(inet_pton(AF_INET,"192.168.2.1",&sin.sin_addr.s_addr) <= 0)
{
        // 错误处理
}
// 网络字节序不能直接输出,需要从网络字节序转换回字符串型才能输出
printf("%s\n",inet_ntop(AF_INET,&sin.sin_addr.s_addr,buf,sizeof(buf)));

TCP客户端服务器编程模型

  • 客户端调用序列

    • 调用socket函数创建套接字

    • 调用connect连接服务器端

    • 调用I/O函数(read/write)与服务器端通讯

    • 调用close关闭套接字

  • 服务器端调用序列

    • 调用socket函数创建套接字

    • 调用bind绑定本地地址和端口

    • 调用listen启动监听

    • 调用accept从已连接队列中提取客户连接,没有客户连接会阻塞

    • 调用I/O函数(read/write)与客户端通讯

    • 调用close关闭套接字

套接字与地址绑定

绑定地址

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

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>
查找绑定到套接字的地址

int getsockname(inf sockfd,struct sockaddr*restrict addr,socklen_t*restrict alenp);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>
获取对方地址

int getpeername(int sockfd,struct sockaddr*restrict addr,socklen_t*restrict alenp);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>

建立连接

服务器端

int listen(int sockfd,int backlog);

  • 返回:成功返回0,出错返回-1。backlog指定进行客户端连接排队的队列长度

  • 头文件

#include <sys/socket.h>

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

客户端

int connect(int sockfd,const struct sockaddr* addr,socklen_t len);

  • 返回:成功返回0,出错返回-1

  • 头文件

#include <sys/socket.h>

特殊bind地址

  • 一台主机可以有多个网络接口和多个IP地址,如果我们只关心某个地址的连接请求,我们可以指定一个具体的IP地址,如果要响应所有接口上的连接请求就要使用一个特殊的地址INADDR_ANY

  • #define INADDR_ANY (uint32_t)0x00000000

// 监听所有服务器上ip所得到的连接请求
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_addr.s_addr = INADDR_ANY;

tcp示例

服务器端代码
#include <stdio.h>
// atoi函数
#include <stdlib.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// 服务器socket描述符
int server_fd = 0;
// 服务器连接属性
struct sockaddr_in server_attr;
// 客户端socket描述符
int client_fd = 0;
// 客户端连接属性
struct sockaddr_in client_attr;
// ctrl + c 关闭服务器监听
void sigint_handle(int);
// 响应客户端连接
void response(int);
// 服务器连接日志
void server_log(struct sockaddr_in*);
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        memset(&server_attr,0,sizeof(server_attr));
        memset(&client_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }
        /*
         * 2. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 3. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }
        socklen_t addrlen = sizeof(client_attr);
        /*
         * 4. 调用accept获得客户端连接,并返回一个新的socket文件描述符
         * 若没有客户端连接,此函数会阻塞,直到获得一个客户端连接
         */
        while(1)
        {
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                server_log(&client_attr);
                /*
                 * 5. 调用I/O函数(read/write)和连接的客户端进行双向的通信
                 */
                response(client_fd);
                close(client_fd);
        }
        close(server_fd);
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void response(int fp)
{
        time_t rawtime;
        struct tm* timeinfo;
        time(&rawtime);
        timeinfo = localtime(&rawtime);
        char str[39] = "I am server. ";
        strcat(str,asctime(timeinfo));
        write(fp,str,sizeof(str));
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
服务器端代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        int client_fd = socket(AF_INET,SOCK_STREAM,0);
        struct sockaddr_in client_attr;
        memset(&client_attr,0,sizeof(client_attr));
        client_attr.sin_family = AF_INET;
        // 主机字节序转换成网络字节序
        client_attr.sin_port = htons((short)atoi(argv[2]));
        inet_pton(AF_INET,argv[1],&client_attr.sin_addr.s_addr);
        // client_attr.sin_addr.s_addr = 
        if(-1 == connect(client_fd,(struct sockaddr*)&client_attr,sizeof(client_attr)))
        {
                perror("connect ");
                return 1;
        }
        char res[39] = {'\0'};
        read(client_fd,res,sizeof(res));
        printf("buf = %s\n",res);
        close(client_fd);
        return 0;
}
自定义协议服务器端,多进程处理并发
#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// 服务器socket描述符
int server_fd = 0;
// 服务器连接属性
struct sockaddr_in server_attr;
// 客户端socket描述符
int client_fd = 0;
// 客户端连接属性
struct sockaddr_in client_attr;
// ctrl + c 关闭服务器监听
void sigint_handle(int);
//  回收子进程方法
void sigchld_handle(int);
// 响应客户端连接
void response(int);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// 自定义写
int my_write(int fd,char* str);
// 自定义读
int my_read(int fd,char* str);
/* 自定义协议结构体 */
typedef struct{
        int bytes;
        char data[512];
}my_protocol;
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        memset(&server_attr,0,sizeof(server_attr));
        memset(&client_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }
        /*
         * 2. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 3. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }
        socklen_t addrlen = sizeof(client_attr);
        /*
         * 4. 调用accept获得客户端连接,并返回一个新的socket文件描述符
         * 若没有客户端连接,此函数会阻塞,直到获得一个客户端连接
         */
        int pid = 0;
        // 注册SIGCHLD信号处理函数,回收子进程,避免僵尸进程的产生
        signal(SIGCHLD,sigchld_handle);
        while(1)
        {
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                // 子线程处理I/O,达到并发的效果
                pid = fork();
                if(pid == 0)
                {
                        /*
                        * 5. 调用I/O函数(read/write)和连接的客户端进行双向的通信
                        */
                        response(client_fd);
                        close(client_fd);
                        break;
                }
                close(client_fd);
        }
        close(server_fd);
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        exit(0);
}
void response(int fp)
{
        time_t rawtime;
        struct tm* timeinfo;
        char client_data[512];
        int read_len = 0;
        while(1)
        {
                memset(client_data,'\0',sizeof(client_data));
                time(&rawtime);
                timeinfo = localtime(&rawtime);
                if((read_len = my_read(fp,client_data)) == -1)
                {
                        break;
                }
                else if(read_len == 0)
                {
                        strcat(client_data,"incorrect data\n");
                }
                else
                {
                        strcat(client_data,"\n");
                        strcat(client_data,asctime(timeinfo));
                }
                if(my_write(fp,client_data) == -1)
                {
                        break;
                }
        }
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
int my_write(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        strcpy(p.data,str);
        p.bytes = strlen(p.data);
        if(write(fd,&p,sizeof(p)) < 0)
        {
                return -1;
        }
        return 0;
}
int my_read(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        int read_len = 0;
        read_len = read(fd,&p,sizeof(p));
        if(p.bytes != strlen(p.data) && read_len != 0)
        {
                return -1;
        }
        strcpy(str,p.data);
        return read_len;
}
//  回收子进程方法
void sigchld_handle(int sig)
{
        // signal(SIGCHLD,sigchld_handle);
        wait(NULL);
        printf("free\n");
}
自定义协议客户端
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
// 自定义写
int my_write(int fd,char* str);
// 自定义读
int my_read(int fd,char* str);
/* 自定义协议结构体 */
typedef struct{
        int bytes;
        char data[512];
}my_protocol;
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        int client_fd = socket(AF_INET,SOCK_STREAM,0);
        struct sockaddr_in client_attr;
        memset(&client_attr,0,sizeof(client_attr));
        client_attr.sin_family = AF_INET;
        // 主机字节序转换成网络字节序
        client_attr.sin_port = htons((short)atoi(argv[2]));
        inet_pton(AF_INET,argv[1],&client_attr.sin_addr.s_addr);
        // client_attr.sin_addr.s_addr = 
        if(-1 == connect(client_fd,(struct sockaddr*)&client_attr,sizeof(client_attr)))
        {
                perror("connect ");
                return 1;
        }
        char res[512];
        while(1)
        {
                memset(res,'\0',sizeof(res));
                scanf("%s",res);
                if(-1 == my_write(client_fd,res))
                {
                        perror("write");
                        break;
                }
                if(my_read(client_fd,res) <= 0)
                {
                        perror("read");
                        break;
                }
                printf("res = %s",res);
        }
        close(client_fd);
        return 0;
}
int my_write(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        strcpy(p.data,str);
        p.bytes = strlen(p.data);
        if(write(fd,&p,sizeof(p)) < 0)
        {
                return -1;
        }
        return 0;
}
int my_read(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        int read_len = 0;
        read_len = read(fd,&p,sizeof(p));
        if(p.bytes != strlen(p.data) && read_len != 0)
        {
                return -1;
        }
        strcpy(str,p.data);
        return read_len;
}
tcp自定义协议服务器端,多线程处理并发
#include <stdio.h>
// atoi函数
#include <stdlib.h>
// wait函数
#include <sys/wait.h>
// I/O函数
#include <unistd.h>
#include <time.h>
// socket 系列函数
#include <sys/socket.h>
// sockaddr_in 结构体
#include <netinet/in.h>
// hton 函数
#include <arpa/inet.h>
#include <signal.h>
// memset函数
#include <string.h>
// pthread库
#include <pthread.h>
// 服务器socket描述符
int server_fd = 0;
// 服务器连接属性
struct sockaddr_in server_attr;
// 客户端socket描述符
int client_fd = 0;
// 客户端连接属性
struct sockaddr_in client_attr;
// ctrl + c 关闭服务器监听
void sigint_handle(int);
//  回收子进程方法
void sigchld_handle(int);
// 响应客户端连接
void response(int);
// 服务器连接日志
void server_log(struct sockaddr_in*);
// 自定义写
int my_write(int fd,char* str);
// 自定义读
int my_read(int fd,char* str);
// 多线程处理并发函数
void* thread_handle(void* data);
/* 自定义协议结构体 */
typedef struct{
        int bytes;
        char data[512];
}my_protocol;
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parameter\n");
                return 1;
        }
        if(SIG_ERR == signal(SIGINT,sigint_handle))
        {
                perror("signal error");
                return 1;
        }
        memset(&server_attr,0,sizeof(server_attr));
        memset(&client_attr,0,sizeof(server_attr));
        /*
         * 1. 创建socket
         * socket创建在内核中,是一个结构体
         * AF_INET : IPV4
         * SOCK_STREAM : TCP协议
         */
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd == -1)
        {
                perror("sock error");
                return 1;
        }
        /*
         * 2. 调用bind函数将socket与地址(ip,port)进行绑定
         */
        server_attr.sin_family = AF_INET;
        // 转换位网络字节序
        server_attr.sin_port = htons((short)atoi(argv[1]));
        // 表示所有ip,也可以指定第一个ip,指定ip时要把主机字节序转换成网络字节序
        server_attr.sin_addr.s_addr = INADDR_ANY;
        // 绑定ip端口
        if(-1 == bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr)))
        {
                perror("bind error");
                return 1;
        }
        /*
         * 3. 调用listen函数启动监听,通知系统接收来自客户端的请求
         * 第二个参数代表客户端队列的长度
         * 执行成功后能用netstat查看
         */
        if(-1 == listen(server_fd,8))
        {
                perror("listen error");
                return 1;
        }
        socklen_t addrlen = sizeof(client_attr);
        /*
         * 4. 调用accept获得客户端连接,并返回一个新的socket文件描述符
         * 若没有客户端连接,此函数会阻塞,直到获得一个客户端连接
         */
        pthread_t tid = 0;
        // 初始化线程属性,用来创建分离的线程,使子线程结束后自动回收
        pthread_attr_t thread_attr;
        pthread_attr_init(&thread_attr);
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 注册SIGCHLD信号处理函数,回收子进程,避免僵尸进程的产生
        signal(SIGCHLD,sigchld_handle);
        // 忽略SIGPIPE信号处理函数,避免由于客户端断开连接,并由于read发送SIGPIPE信号导致进程结>束
        signal(SIGPIPE,SIG_IGN);
        while(1)
        {
                client_fd = accept(server_fd,(struct sockaddr*)&client_attr,&addrlen);
                if(client_fd <= 0)
                {
                        continue;
                }
                server_log(&client_attr);
                // 子线程处理I/O,达到并发的效果
                pthread_create(&tid,&thread_attr,thread_handle,(void*)client_fd);
                        printf("child thread\n");
                        // break;
                // 线程是共享的进程资源,这里不要关闭,不然线程里面的也没了
                // close(client_fd);
        }
        // close(server_fd);
        sleep(3);
        return 0;
}
void sigint_handle(int sig)
{
        close(server_fd);
        close(client_fd);
        exit(0);
}
void response(int fp)
{
        time_t rawtime;
        struct tm* timeinfo;
        char client_data[512];
        int read_len = 0;
        while(1)
        {
                memset(client_data,'\0',sizeof(client_data));
                time(&rawtime);
                timeinfo = localtime(&rawtime);
                if((read_len = my_read(fp,client_data)) == -1)
                {
                        break;
                }
                else if(read_len == 0)
                {
                        strcat(client_data,"incorrect data\n");
                }
                else
                {
                        strcat(client_data,"\n");
                        strcat(client_data,asctime(timeinfo));
                }
                if(my_write(fp,client_data) == -1)
                {
                        break;
                }
        }
}
void server_log(struct sockaddr_in* client_attr)
{
        char ip[16] = {'\0'};
        // 结构体的的数据不能直接显示,需要先转换成本机字节序
        inet_ntop(AF_INET,&client_attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connected by %s:%d\n",ip,ntohs(client_attr->sin_port));
}
int my_write(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        strcpy(p.data,str);
        p.bytes = strlen(p.data);
        if(write(fd,&p,sizeof(p)) < 0)
        {
                return -1;
        }
        return 0;
}
int my_read(int fd,char* str)
{
        my_protocol p = {0};
        memset(&p.data,'\0',sizeof(p.data));
        int read_len = 0;
        read_len = read(fd,&p,sizeof(p));
        if(p.bytes != strlen(p.data) && read_len != 0)
        {
                return -1;
        }
        strcpy(str,p.data);
        return read_len;
}
//  回收子进程方法
void sigchld_handle(int sig)
{
        // signal(SIGCHLD,sigchld_handle);
        wait(NULL);
        printf("free\n");
}
void* thread_handle(void* data)
{
        int fd = (int)data;
        /*
        * 5. 调用I/O函数(read/write)和连接的客户端进行双向的通信
        */
        response(fd);
        close(fd);
        return (void*)NULL;
}

udp编程

udp编程模型

  • 客户端调用序列

    • 调用socket函数创建套接字

    • 调用bind连接服务器端(这一步可以不要)

    • 调用sendto/readfrom与服务器端通讯

    • 调用close关闭套接字

  • 服务器端调用序列

    • 调用socket函数创建套接字

    • 调用bind绑定本地地址和端口

    • 调用sendto/readfrom与客户端通讯

    • 调用close关闭套接字

数据传输

发送数据

ssize_t send(int sockfd,const void* buf,size_t nbytes,int flag);

  • 返回:成功返回发送字节数,出错返回-1

  • 功能:发送数据,需要在发送数据前已经建立的连接,多用于TCP,用于UDP时要使用connect函数先建立连接
    `ssize_t sendto(int sockfd,const void buf,size_t nbytes,int flag,const struct sockaddr

desaddr,socklen_t destlen);`

  • 返回:成功返回发送的字节数,出错返回-1

  • 功能:发送数据,需要指定发送方(desaddr参数)

  • sockaddr:目的地的相关信息
    ssize_t sendmsg(int sockfd,const struct msghdr* msg,int flag);

  • 返回:成功返回发送的字节数,出错返回-1

  • 头文件

#include <sys/socket.h>
  • msghdr结构体

struct msghdr
{
        void* msg_name;// optional address
        socklen_t msg_namelen;// address size in bytes
        struct iovec* msg_iov;// array of I/O buffers
        int msg_iovlen;// number of elements in array
        void* msg_control;// ancillary data
        socklen_t msg_controllen;// number of ancillary bytes
        int msg_flags;// flag for received message
};
接收数据

ssize_t recv(int sockfd,void* buf,size_t nbytes,int flag);

  • 功能:接收数据,不能获得发送方的信息,
    ssize_t recvfrom(int sockfd,void*restrict buf,size_t len,int flag,struct sockaddr*restrict addr,socklen_t*restrict addrlen);

  • 功能:除了能获取数据外,还可以获得发送方的信息,用于在之后对指定的发送方发送数据
    ssize_t recvmsg(int sockfd,struct msghdr* msg,int flag);

  • 头文件

#include <sys/socket.h>

示例

服务器端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <pthread.h>
typedef struct
{
        struct sockaddr_in attr;
        char data[512];
}thread_data;
int server_fd = 0;
void* thread_handle(void* client);
void server_log(struct sockaddr_in* attr);
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parametr\n");
                return 1;
        }
        /*
         * 1. 创建socket
         * SOCK_DGRAM : 使用UDP传输
         */
        server_fd = socket(AF_INET,SOCK_DGRAM,0);

        /*
         * 2. 设置服务器选项,SO_REUSEADDR,会先解除之前的绑定,在绑定一次,避免重启时因为连接还
没有断开导致的bind报错
         */
        setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,(void*)NULL,0);

        /*
         * 3. 设置服务器选项,SO_SNDTIMEO,发送客户端消息的超时时间,避免子线程阻塞
         */
        struct timeval time_out = {3,0};
        // setsockopt(server_fd,SOL_SOCKET,SO_RCVTIMEO,(char*)&time_out,sizeof(time_out));
        setsockopt(server_fd,SOL_SOCKET,SO_SNDTIMEO,(char*)&time_out,sizeof(time_out));

        /*
         * 4. 设置绑定的ip及端口信息
         */
        struct sockaddr_in server_attr;
        server_attr.sin_family = AF_INET;
        server_attr.sin_port = htons((short)atoi(argv[1]));
        server_attr.sin_addr.s_addr = INADDR_ANY;

        /*
         * 5. 绑定本地ip,port
         */
        bind(server_fd,(struct sockaddr*)&server_attr,sizeof(server_attr));
        // 存放客户端信息
        thread_data* client;
        // 存放子线程id
        pthread_t tid = 0;
        // 存放子线程属性,用以设置以分离模式启动子线程
        pthread_attr_t thread_attr;
        memset(&thread_attr,0,sizeof(thread_attr));
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
        // 上次是否读取成功的标识,用以判断是否需要重新申请内存
        int recv_flag = 0;
        // 连接信息结构体的大小
        socklen_t addrlen = sizeof(struct sockaddr_in);
        while(1)
        {
                /*
                * 6. 获得客户端发送的数据与客户端信息,用于与客户端通信
                */
                if(recv_flag == 0)
                {
                        client = malloc(sizeof(thread_data));
                }
                memset(client,0,sizeof(struct sockaddr_in));
                if(0 >= recvfrom(server_fd,client->data,sizeof(client->data),0,(struct sockaddr*)&client->attr,&addrlen))
                {
                        // 超时或者读取失败则继续下一次等待,设置标识,避免下次重新申请内存
                        printf("time out\n");
                        recv_flag = 1;
                        continue;
                }
                server_log(&client->attr);
                /*
                * 7. 使用子线程处理并发
                */
                pthread_create(&tid,&thread_attr,thread_handle,(void*)client);
                recv_flag = 0;
        }
        return 0;
}
void* thread_handle(void* client)
{
        thread_data* t = (thread_data*)client;
        char thread_id[20] = {'\0'};
        sprintf(thread_id,"%lu",pthread_self());
        // 连接信息结构体的大小
        strcat(t->data,"\n;thread_id = ");
        strcat(t->data,thread_id);
        sendto(server_fd,t->data,sizeof(t->data),0,(struct sockaddr*)&t->attr,sizeof(t->attr));
        // 释放动态申请的内存
        free(client);
        return NULL;
}
void server_log(struct sockaddr_in* attr)
{
        char ip[16] = {'\0'};
        inet_ntop(AF_INET,&attr->sin_addr.s_addr,ip,sizeof(ip));
        printf("connect from %s handle by %lu\n",ip,pthread_self());
}
udp客户端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <pthread.h>
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incorrect parametr\n");
                return 1;
        }
        /*
         * 1. 创建socket
         * SOCK_DGRAM : 使用UDP传输
         */
        int client_fd = socket(AF_INET,SOCK_DGRAM,0);


        /*
         * 3. 设置服务器的ip及端口信息
         */
        struct sockaddr_in server_attr;
        server_attr.sin_family = AF_INET;
        server_attr.sin_port = htons((short)atoi(argv[2]));
        inet_pton(AF_INET,argv[1],&server_attr.sin_addr.s_addr);
        char res[512] = {'\0'};
        while(1)
        {
                scanf("%s",res);
                if(sendto(client_fd,res,sizeof(res),0,(struct sockaddr*)&server_attr,sizeof(server_attr)) <= 0)
                {
                        perror("sendto");
                        break;
                }
                if(recv(client_fd,res,sizeof(res),0) <= 0)
                {
                        perror("recv");
                        break;
                }
                printf("%s\n",res);
        }
        return 0;
}

域名解析函数

hostent结构体

struct hostent
{
        char* h_name;// 主机名
        char** h_aliases;// 别名,字符串数组
        int h_addrtype;// 协议类型
        int h_length;// 网络地址大小
        char** h_addr_list;// 指向网络地址的指针
};

函数

struct hostent* gethostent(void);
struct hostent* gethostbyname(const char* hostname);
void sethostent(int stayopen);
void endhostent(void); // 与hostent一起使用

  • 头文件

#include <netdb.h>

示例

if((hptr = gethostbyname("www.jinblog.com")) == NULL)
{
        fprintf(stderr,"gethostbyname call faile. %s\n",hsterror(h_errno));
        return 1;
}
printf("official name:%s\n",hptr->h_name);
for(pptr = hptr->h_aliases;*pptr != NULL;pptr++)
{
        printf("\talias:%s\n",*pptr);
}
if(hptr->h_addrtype != AF_INET)
{
        fprintf(stderr,"invalid address type %d\n",hptr->h_addrtype);
}
pptr = hptr->h_addr_list;
for(;*pptr != NULL;pptr++)
{
        printf("\taddress:%s\n",inet_ntop(hptr->h_addrtype,*pptr,str,sizeof(str)));
}
打印
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
void print_host(struct hostent* host);
int main(void)
{
        printf("----------gethostent start--------\n");
        struct hostent* host;
        while((host = gethostent()) != NULL)
        {
                print_host(host);
        }
        endhostent();
        printf("----------gethostent  stop--------\n");
        
        printf("\n\n----------gethostentbyname start--------\n");

        if((host = gethostbyname("jinblog.com")) != NULL)
        {
                print_host(host);
        }

        printf("----------gethostentbyname  stop--------\n");
        return 0;
}
void print_host(struct hostent* host)
{
        printf("host : %s\n",host->h_name);
        int i = 0;
        while(host->h_aliases[i] != NULL)
        {
                printf("\talias : %s\n",host->h_aliases[i]);
                i++;
        }
        switch(host->h_addrtype)
        {
                case AF_INET:
                        printf("address type : AF_INET\n");
                        break;
                case AF_INET6:
                        printf("address type : AF_INET6\n");
                        break;
                case AF_UNIX:
                        printf("address type : AF_UNIX\n");
                        break;
                default:
                        printf("address type : unknown\n");
                        break;
        }
        printf("length : %d\n",host->h_length);
        i = 0;
        char ip[32] = {'\0'};
        while(host->h_addr_list[i] != NULL)
        {
                inet_ntop(host->h_addrtype,host->h_addr_list[i],ip,sizeof(ip));
                printf("\tip : %s",ip);
                i++;
                memset(ip,'\0',sizeof(ip));
        }
        printf("\n\n\n");
}

广播

  • 实现一对多的通讯

  • 通过向广播地址发送数据报文实现

套接字选项

  • 套接字选项用于修饰套接字以及其底层通讯协议的各种行为。函数setsockopt和getsockopt可以查看和设置套接字的各种选项
    int getsockopt(int sockfd,int optname,void* optval,socklen_t* optlen);

int setsockopt(int sockfd,int optname,void* optval,socklen_t* optlen);

SO_BROADCAST选项

  • SO_BROADCAST选项控制着UDP套接字是否能够发送广播数据报,选项的类型位int,非零意味着是,注意,只有UDP套接字可以使用这个选项,TCP是不能使用广播的

int opt = 1;
if((sockfd = sock(AF_INET,SOCK_DGRAM,0)) < 0)
{
        // 错误处理
}
if(setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(int)) < 0)
{
        // 错误处理
}

SO_SNDBUF和SO_RCVBUF选项

  • 每个套接字有一个发送缓冲区和接收缓冲区,这两个缓冲区由底层协议使用,接收缓冲区存放由协议接收的数据直到被应用程序读走,发送缓冲区存放应用写出的数据直到被协议发送出去。SO_SNDBUF和SO_RCVBUF选项分别控制发送和接收缓冲区的大小,他们类型为int,以字节为单位

if((sockfd = sock(AF_INET,SOCK_DGRAM,0)) < 0)
{
        // 错误处理
}
int opt = 0;
if(getsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&opt,sizeof(opt)) < 0)
{
        // 错误处理
}
opt += 888;
if(setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&opt,sizeof(opt)) < 0)
{
        // 错误处理
}

广播地址

  • 如果用{netID,subnetID,hostID}({网络id,子网id,主机id})标识IPV4地址,那么有四类的广播地址,我们用-1表示所有比特都为1的字段

  • 子网广播地址:{netID,subnetID,-1},这类地址编排指定子网上的所有接口,例如,如果我们对B类地址192.168采用8为子网id,那么192.168.2.255将是192.168.2子网上的所有接口的子网广播地址。路由器不转发这类广播

  • 全部子网地址:{netID,-1,-1}。这类广播地址编排指定网络上的所有子网。如果说这类地址被使用过的话,那么现在已很少见了

  • 受限广播地址{-1,-1,-1}或255.255.255.255。路由器从不转发目的地址为255.255.255.255的IP报数据。

示例

广播服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc,char* argv[])
{
        if(argc < 3)
        {
                printf("incrrect parameter\n");
                return 1;
        }
        /*
         * 1. 创建socket,要创建UDP类型的socket,广播包是用UDP发送的
         */
        int server_fd = socket(AF_INET,SOCK_DGRAM,0);

        /*
         * 2. 设置要发送到的广播域和port
         */
        struct sockaddr_in des_sock_attr;
        des_sock_attr.sin_family = AF_INET;
        // 注意argv[1]是广播地址
        inet_pton(AF_INET,argv[1],&des_sock_attr.sin_addr.s_addr);
        des_sock_attr.sin_port = htons((short)atoi(argv[2]));

        /*
         * 2. 设置socket选项,使socket可以发送广播报
         */
        int opt = 1;
        setsockopt(server_fd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(int));
        char buf[512] = {'\0'};
        int i = 0;
        while(1)
        {
                sprintf(buf,"message %d\n",i);
                if(0 >= sendto(server_fd,buf,sizeof(buf),0,(struct sockaddr*)&des_sock_attr,sizeof(des_sock_attr)))
                {
                        perror("sendto");
                        continue;
                }
                printf("broadcast : %d\n",i);
                sleep(3);
                i++;
                memset(buf,'\0',sizeof(buf));
        }
        return 1;
}
广播客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(int argc,char* argv[])
{
        if(argc < 2)
        {
                printf("incrrect parameter\n");
                return 1;
        }
        /*
         * 1. 创建socket,要创建UDP类型的socket,广播包是用UDP发送的
         */
        int client_fd = socket(AF_INET,SOCK_DGRAM,0);
        /*
         * 2. 设置socket,ip,port等属性
         */
        struct sockaddr_in client_attr;
        client_attr.sin_port = htons((short)atoi(argv[1]));
        client_attr.sin_addr.s_addr = INADDR_ANY;
        client_attr.sin_family = AF_INET;

        /*
         * 3. 设置 SO_REUSEADDR,避免bind报错
         */
        setsockopt(client_fd,SOL_SOCKET,SO_REUSEADDR,(void*)NULL,0);
        /*
         * 4. bind绑定ip端口
         */
        bind(client_fd,(struct sockaddr*)&client_attr,sizeof(client_attr));

        /*
         * 5. 接收广播报数据
         */
        struct sockaddr_in server_attr;
        char buf[512] = {'\0'},ip[16] = {'\0'};
        socklen_t addrlen = sizeof(server_attr);
        while(1)
        {
                if(recvfrom(client_fd,buf,sizeof(buf),0,(struct sockaddr*)&server_attr,&addrlen) <= 0)
                {
                        perror("recvfrom");
                        return 1;
                }
                inet_ntop(AF_INET,&server_attr.sin_addr.s_addr,ip,sizeof(ip));
                printf("server ip : %s;\nbroadcast data : %s\n\n",ip,buf);
        }
        return 0;
}

下一篇:http://jinblog.com/archives/868.html

标签: c, linux c网络编程

添加新评论