一、网络通信简介
第一部分内容,暂时没法描述,内容实在太多,待后续专门的系列文章。
二、linux网络通信
在linux中继承了Unix下“一切皆文件”的思想, 在linux中要实现网络通信需要创建相关的网络文件;linux中
用相关的系统调用创建相关的网络文件。
1、网络服务器实现(基于TCP/IP)
要实现一个网络服务器,则按照以下步骤完成
(1)创建网络套接字文件
socket( )系统调用用来创建网络套接字。其原型如下:
NAME socket - create an endpoint for communication //功能: 用来创建一个终端的链接 SYNOPSIS #include <sys/types.h> #include <sys/socket.h> int socket(int domain, //这个参数用来选择网络协议族, int type, //用来指定套接类型, int protocol); //这个参数一般指定为0 除非用于创建原始协议族时才设置为其他值
返回值:
成功返回套接字文件描述符,失败返回-1;
创建网络套接字文件的代码如下:
#include <sys/socket.h> int socketfd ; socketfd=socket(PF_INET,SOCK_STREAM,0); if(-1 == socketfd) { perror("service"); return socketfd; }
(2)、绑定本地端口
通过系统调用 bind( )实现服务器与本地端口的绑定, 其原型如下所示:
NAME bind - bind a name to a socket //为套接字指定名称 SYNOPSIS #include <sys/types.h> #include <sys/socket.h> int bind( int sockfd, //套接字文件描述符 const struct sockaddr *my_addr, //通用的套接字地址指针 socklen_t addrlen); //第二个参数占用的字节数 sizeof(my_addr) DESCRIPTION bind() gives the socket sockfd the local address my_addr. my_addr is addrlen bytes long. Traditionally, this is called “assigning a name to a socket.” When a socket is created with socket(2), it exists in a name space (address family) but has no name assigned.
返回值:
绑定成功返回0; 失败返回-1;
关于第二个参数还需要说明:
函数原型中指定的是一个通用的指针,而在应用具体的协议的时候,这个参数需要传递与具体协议相关
的指针。 这个通用结构体类型的定义如下:
struct sockaddr的定义如下:
/* * 1003.1g requires sa_family_t and that sa_data is char. */ struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ };
例如: 当用TCP/IP协议的时候,就需要使用 struct sockaddr_in 类型结构体变量的指针。
struct sockaddr_in 定义如下:
struct sockaddr_in { sa_family_t sin_family; /* Address family */ //socket 网络协议族类型 __be16 sin_port; /* Port number */ //要绑定的本地端口号 struct in_addr sin_addr; /* Internet address*/ //本机的IP地址 /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; // 第四个域通常不用赋值 };
要点:
.sin_port 的赋值,需要进行类型转换,
.sin_addr是一个struct in_addr 结构体类型,这个结构体赋值也需要注意。
struct in_addr 的定义如下: /* Internet address. */ struct in_addr { __be32 s_addr; };
typedef __u32 __bitwise __be32; //32位的无符号整数
因为我们通常用 192.168.0.12 这种格式来表示网络的IP地址,而struct sockaddr_in 的sin_addr.s_addr 是一个
32位的无符号整数表示的IP地址,因此需要将 192.168.0.12 这种格式进行转换才能进行赋值。
下面的函数族用来在 192.168.0.12 和 32bit 的网络IP地址之间的转换。
NAME inet_aton, inet_addr, inet_network, inet_ntoa, inet_makeaddr, inet_lnaof, inet_netof - Internet address manipulation routines SYNOPSIS #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); //将字符串"192.168.0.12"转换为32bit的IP地址 in_addr_t inet_network(const char *cp); char *inet_ntoa(struct in_addr in); //将32bit的IP地址转换为 "192.168.0.12"字符串 struct in_addr inet_makeaddr(int net, int host); in_addr_t inet_lnaof(struct in_addr in); in_addr_t inet_netof(struct in_addr in);
绑定本地端口的示例代码如下:
#include <sys/socket.h> #include <netinet/in.h> struct sockaddr_in bind_addr; socklen_t addr_len; //绑定本地端口,即为socketfd命名 bind_addr.sin_family = AF_INET; bind_addr.sin_port= htons (6000); //通讯端口,服务器端和客户机端必须一样 bind_addr.sin_addr.s_addr= inet_addr("192.168.0.101"); //本机的IP地址 addr_len=sizeof(struct sockaddr_in); ret = bind(socketfd,(struct sockaddr*)&bindaddr, addr_len); if(-1==ret) { perror("bind"); return ret; }
(3)监听本地端口
在绑定本地端口后,就需要对本地的端口进行监听,用以检测是否有客户机向服务器发送服务的请求。用
系统调用 listen( )建立监听。 其原型如下
LISTEN(2) Linux Programmer’s Manual LISTEN(2) NAME listen - listen for connections on a socket SYNOPSIS #include <sys/socket.h> int listen(int sockfd, //要监听的网络套接字文件描述符 int backlog); //可以服务的最大客户端数目,或者说监听队列的长度
返回值:
成功返回0 ;失败返回-1;
示例代码如下:
#include <sys/socket.h> socketfd = listen(socketfd, 10);
(4) 接受服务请求并创建本地缓冲文件
当监听到服务请求后,就需要对服务请求做出响应,同时还需要创建一个用来缓存数据信息的文件,通过 accept( )
系统调用来对从监听队列取出服务请求,同时创建一个缓存数据信息的文件。
accept( )系统调用的原型如下:
ACCEPT(2) Linux Programmer’s Manual ACCEPT(2) NAME accept - accept a connection on a socket SYNOPSIS #include <sys/types.h> #include <sys/socket.h> int accept( int sockfd, //要监听的网络套接字文件的描述符 struct sockaddr *addr, //输出参数,用来存放接收到的客户机的网络地址信息 socklen_t *addrlen); //输出参数,用来存放客户机的网络地址信息的长度
返回值:
成功返回数据缓冲文件描述符,失败返回 -1 。
第二参数需要注意,因为函数原型使用的一个通用的数据类型,而实际应用中需要根据网络协议的不同,需要采用不同
的数据类型指针。
例如这里使用 TCP/IP 因此需要传递一个 struct sockaddr_in 结构体变量的指针。
下面为响应服务并创建数据缓冲文件的Exp:
#include <sys/socket.h> struct sockaddr_in client_addr; socklen_t client_addr_len; //响应监听队列的请求,并创建缓存数据文件 client_addr_len = sizeof(struct sockaddr_in); sockbuf_fd=accept(socketfd,(struct sockaddr*)&client_addr, &client_addr_len); if(-1 == sockbuf_fd) { perror("accept"); return sockbuf_fd; }
(5) 接受和发送数据
accept( )系统调用从请求服务队列取得一个服务请求,同时为这个服务请求建立一个缓存数据信息的文件,同时
返回创建的缓存数据信息的文件描述符,因此可以和访问普通文件一样来访问缓存数据信息文件; 对缓存数据信息
文件进行读就相当于接收数据,对缓存数据信息文件进行写就相当于发送数据。
Exp: //发送数据 ret=write(sock_buf_fd,"socket network serives test.\n",29); if(-1 == ret) { perror("send data"); return ret; } //接收数据 memset(sock_buf,0,sizeof(sockbuf)); ret=read(sock_buf_fd,sock_buf,sizeof(sock_buf)); if(-1 == ret) { perror("read socket"); return ret; }
(6)关闭文件
在调用 socket( ) 和accept( ) 时候都会打开一个网络套接字相关的文件,因此需要关闭;关闭文件调用close( )完成即可。
Exp:
close(sock_buf_fd);
close(socket_fd);
2、网络客户端实现(基于TCP/IP)
要通过网络进行通讯,在网络客户端也需要可以分几步进行:
(1) 创建网络套接字文件
创建套接字的方法,与服务器端一样,且使用的网络族协议和套接字类型与服务端一样。
Exp:
int socket_fd; socket_fd = socket( PF_INET, SOCK_STREAM, 0 );
(2) 请求建立连接
在客户端为了获取服务端的服务,必须申请建立与服务器的连接。在客户端通过 connect( )请求建立服务连接, 原型
如下:
CONNECT(2) Linux Programmer’s Manual CONNECT(2) NAME connect - initiate a connection on a socket SYNOPSIS #include <sys/types.h> #include <sys/socket.h> int connect( int sockfd, //建立的套接字文件 const struct sockaddr *serv_addr, //指定要连接到服务的地址信息结构体的指针 socklen_t addrlen); //参数2 的长度
返回值:
成功返回0 ;失败返回-1 。
要点:
这个函数定义的时候,采用的是一个通用的指针;在应用程序中,需要传递指定通讯协议的数据结构的指针。
请求建立连接到示例代码如下:
#include <sys/socket.h> #include <netinet/in.h> int ret; struct sockaddr_in srv_addr; srv_addr.sin_family =AF_INET; srv_addr.sin_port= htons(6000); srv_addr.sin_addr.s_addr= inet_addr("192.168.0.101"); ret =connect(socket_fd , (struct sockaddr*)&srv_addr , sizeof(struct sockaddr_in) )
(3) 接收和发送数据
在建立通讯连接后,就可以进行数据的接收和发送了,可以和服务器端一样进行数据的读写。
示例代码:
memset(buf, 0 , sizeof(buf) ); //将缓冲区域清空的原因是定义的buf里面存在一些其他数据,如果不清空,数据将错误 ret=read(socket_fd,buf,sizeof(buf)); if(ret<0) { perror("read"); return ret; } ret=write(socket_fd,buf,strlen(buf)); //如果在读之前不清空 buf ,那么这个地方 strlen(buf) 就要改为 ret ; if(ret<0) { perror("write"); return ret; }
要点:
不清空、或者 不将 strlen(buf) 改为 ret 的执行结果为:
[root@localhost tcpip]# ./service
socket network serives test.
�@V@�R@PP@�R@qr@. //打印出现乱码。
socket network serives test.
�@V@�R@PP@�R@qr@. //打印出现乱码。
下面是完整的一个可以通过TCP/IP 进行通信的示例代码:
Exp: service.c 用于当作服务程序
//本文件用来测试网络通信 #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> int main(int argc,char* argv[]) { int socket_fd; int ret; struct sockaddr_in bind_addr; struct sockaddr_in client_addr; socklen_t addr_len; int sock_buf_fd; char sock_buf[1024]; //创建网络套接字 socket_fd=socket(PF_INET,SOCK_STREAM,0); if(-1 == socket_fd) { perror("service"); return socket_fd; } //绑定本地端口,即为socket_fd命名 bind_addr.sin_family = PF_INET; bind_addr.sin_port= htons (6000); bind_addr.sin_addr.s_addr= inet_addr("192.168.0.101"); addr_len=sizeof(struct sockaddr_in); ret = bind(socket_fd,(struct sockaddr*)&bind_addr, addr_len); //监听端口 ret=listen(socket_fd, 10); if(-1 == ret) { perror("listen"); return ret; } //响应监听队列的请求,并创建缓存数据文件 sock_buf_fd=accept(socket_fd,(struct sockaddr*)&client_addr,&addr_len); if(-1 == sock_buf_fd) { perror("accept"); return sock_buf_fd; } //发送数据 ret=write(sock_buf_fd,"socket network serives test.\n",29); if(-1 == ret) { perror("send data"); return ret; } //接收数据 memset(sock_buf,0,sizeof(sock_buf)); ret=read(sock_buf_fd,sock_buf,sizeof(sock_buf)); if(-1 == ret) { perror("read socket"); return ret; } printf("%s\n",sock_buf); close(sock_buf_fd); close(socket_fd); return 0; }
client.c 当作客户程序
//本文件用来测试linux网络编程 #include <stdio.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <string.h> int main(int argc,char*argv[]) { int ret; int socket_fd; struct sockaddr_in srv_addr; socklen_t addr_len; char buf[1024]; //创建网络套接字文件 socket_fd=socket(PF_INET,SOCK_STREAM,0); if(-1 == socket_fd ) { perror("socket"); return socket_fd; } //申请建立与服务器的连接 srv_addr.sin_family=PF_INET; srv_addr.sin_port=htons(6000); srv_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); //服务器IP地址 addr_len=sizeof(struct sockaddr_in); ret=connect(socket_fd,(struct sockaddr *)&srv_addr, addr_len); if(-1==ret) { perror("connect"); return -1; } memset(buf,0,sizeof(buf)); ret=read(socket_fd,buf,sizeof(buf)); if(ret<0) { perror("read"); return ret; } ret=write(socket_fd,buf,strlen(buf)); if(ret<0) { perror("write"); return ret; } close(socket_fd); return 0; }
执行的效果如下:
服务器端:
[root@localhost tcpip]# ./service //在服务器端运行 service 程序,执行后再等待客户程序请求服务 socket network serives test. //在客户端执行 ./client 后 打印出这个信息 [root@localhost tcpip]#
客户端:
/ # ./client
/ #
过程是:
1、服务器给客户端发送: "socket network serives test.\n" 字符串
2、客户端接收服务器端发送来对数据
3、客户端将接收到数据发送到服务器端
4、服务器端接收客户端发送过来的数据,并将接收到的数据打印
示例1: 简单文件传输程序
虚拟机为服务器: IP为 192.168.0.101
友善之臂的2440开发板: IP为 192.168.0.99
测试为从服务器上下载一个文件到开发板,测试成功可以在开发板的系统上查看到下载到开发板的程序。
下面为头文件: ftp.h
//本文件是用于简单文件传输程序的头文件 #if !defined(__FTP_H__) #define __FTP_H__ #define TEL_LEN 512 //报文数据长度为512字节 #define FILE_EXIST 0x0 #define FILE_NOT_EXIST 0x1 #define END_OF_FILE 0x2 #define VALID_DATA 0x3 typedef struct tel_gram { unsigned char type; char data[TEL_LEN]; }tel_t; typedef struct pthread_type { struct sockaddr_in *sock_in_addr; int sock_buf_fd ; }pthread_type_t; #endif
下面为服务器: service.c
//本文件用来实现一个简单的文件传输服务 #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <pthread.h> #include "ftp.h" #include <fcntl.h> int transfer(pthread_type_t *arg) { int fd; int ret; int size; int sock_buf_fd; char file_name[512]; tel_t telgram; struct sockaddr_in* cli_addr; //输出客户机信息 cli_addr=arg->sock_in_addr; printf("the connet client is %s .\n",inet_ntoa(cli_addr->sin_addr.s_addr) ); #if 0 //首先检测链接是否正常 sock_buf_fd=arg->sock_buf_fd; ret=recv( sock_buf_fd, //接收数据的socket数据缓冲文件 &telgram, //存粗接收数据的缓冲去地址 sizeof(tlegram.type), //要接收的数据长度 0); //接收标志 if(ret != sizeof(tlegram.type)) { printf("network establelish is not well. will exit\n"); return 1; } send(sock_buf_fd,&telgram,sizeof(tlegram.type),0); #endif //接受文件名 sock_buf_fd=arg->sock_buf_fd; ret=recv(sock_buf_fd, file_name,512,0); if(-1==ret) { perror("recv file name"); return 1; } //切换到默认路径,默认文件存放路径为/ftp目录 chdir("/ftp"); if( access(file_name, F_OK) ) { telgram.type=FILE_NOT_EXIST; //如果文件不存在就发送信息给客户端 send(sock_buf_fd,&telgram,sizeof(telgram.type),0); return 1; } //打开要传送的文件 fd=open(file_name,O_RDONLY); if(-1==fd) { return -1; } while(1) { telgram.type=VALID_DATA; ret=read(fd, &telgram.data,TEL_LEN); size=sizeof(telgram); if(ret<TEL_LEN) { telgram.type=END_OF_FILE; size =ret + sizeof(telgram.type); } send(sock_buf_fd,&telgram, size,0); } return 0; } int main(int argc,char* argv[]) { int sock_fd; int sock_buf_fd; int ret; struct sockaddr_in srv_addr; //服务器IP地址、端口信息 struct sockaddr_in cli_addr; //用来存储客户段地址信息 socklen_t srv_addr_len; //保存服务器IP地址、端口信息的结构体数据长度 socklen_t cli_addr_len; //保存客户机IP地址、端口信息的结构体数据长度 pthread_type_t pth_arg; //建立网络套接字文件 sock_fd=socket(PF_INET, //使用TCP/IP协议族 SOCK_STREAM, //使用字符流,TCP协议,需要建立稳定链接 0); //非建立原始协议,必须传 0 if(-1 == sock_fd ) { perror("create socket"); return sock_fd; } //绑定本地端口,即为套接字接口命名 srv_addr.sin_family=PF_INET; //指定绑定的协议 srv_addr.sin_port=htons(6000); //指定通信端口 srv_addr.sin_addr.s_addr=inet_addr("192.168.0.101"); //指定服务起的IP srv_addr_len=sizeof(struct sockaddr_in); ret=bind( sock_fd, //要绑定的套接字文件 (struct sockaddr *)&srv_addr, //服务器网络信息结构体 srv_addr_len); //第二个参数的sizeof if(-1 == ret) { perror("socket bind"); return ret; } //监听端口 ret=listen(sock_fd, //要监听的套解字,即要监听的端口 5); //最大监听队列 if(-1 == ret) { perror("socket liseten"); return ret; } //查看请求,并建立数据缓存文件 while(1) { sock_buf_fd=accept( sock_fd, //输入参数 (struct sockaddr*)&cli_addr, //输出参数 客户机IP &cli_addr_len); //第二个参数的sizeof pth_arg.sock_buf_fd=sock_buf_fd; //数据缓存文件指针传递给线程 pth_arg.sock_in_addr=&cli_addr; ret=transfer(&pth_arg); if(ret) { break ; } } close(sock_buf_fd); close(sock_fd); return 0; }
下面文件为客户端程序: client.c
编译命令为: arm-linux-gcc client.c -o client
如果有两个虚拟机开启来也可完成传输, 一个虚拟机也能完成这个代码的测试。
//本文件实现简单文件传输软件的客户端 #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include "ftp.h" int main(int argc,char* argv[]) { int fd; int ret; int sock_fd; struct sockaddr_in srv_addr; tel_t telgram; int size; //检测程序执行时的参数是否正确 //不正确输出提示信息 if(argc<2 || argc > 3) { printf("Usage: client filename\n"); return 0; } //打开网络套接字文件 sock_fd=socket( PF_INET, //与服器程序一样 SOCK_STREAM, 0 ); if(-1 == sock_fd) { perror("socket"); return 0; } //申请与服务器建立连接 srv_addr.sin_family=PF_INET; srv_addr.sin_port=htons(6000); srv_addr.sin_addr.s_addr=inet_addr("192.168.0.101"); ret=connect(sock_fd,(struct sockaddr*)&srv_addr, sizeof(srv_addr)); if(-1 == ret) { perror("connet to server"); return 0; } //发送文件名给服务器 send(sock_fd,argv[1],strlen(argv[1]),0); recv(sock_fd,&telgram,sizeof(telgram.type),0); if(FILE_NOT_EXIST == telgram.type) { printf("Not such a file in servie mathcine,will quit transfer\n"); return 0; } //创建文件来接受数据 fd=open(argv[1], O_WRONLY | O_CREAT | O_TRUNC); if(-1 == fd ) { perror("create file"); return 0; } while(1) { size=recv(sock_fd, &telgram, sizeof(telgram), 0); if(END_OF_FILE == telgram.type) { ret=write(fd,&telgram.data,size); if(ret<size) { //如果写入的数据比读到的数据少,表示写入错误删除文件 unlink(argv[1]); perror("local file write"); } break; } ret=write(fd, &telgram.data, size); if(ret<size) { //如果写入的数据比读到的数据少,表示写入错误删除文件 unlink(argv[1]); perror("local file write"); break ; } } close(fd); close(sock_fd); return 0; }
【linux草鞋应用编程系列】_5_网络编程
本系列文章欢迎批评指正,欢迎指正错误。
本系列文章,未完待续.........