目录
Linux的网络连接是通过内核完成的,其支持多种网络协议,如TCP/IP、IPX、DDP以及IPv6等。Linux系统通过提供套接字(scoket)进行网络编程。网络程序通过socket和其他几个函数调用后返回一个通信的文件描述符,可以将这个描述符看成普通文件的描述符来操作,并通过对描述符读写操作实现网络间的数据交流。
1.网络编程常识
TCP/IP协议是一组在网络中提供可靠数据传输和非可靠数据服务的协议。该协议组中最主要的协议就是TCP协议和IP协议,当然还包括其他协议,例如ICMP、ARP、PPP等协议。提供网络可靠传输(面向连接)的称为TCP协议,提供非可靠(面向无连接)传输的称为UDP。
TCP/IP协议参考模型如图所示。
应用层 | FTP,Telnet,HTTP | |||
传输层 | TCP,UDP | |||
网络互联层 | IP | |||
主机联网层 | 以太网 | 令牌环网 | 802.2 | HDLC,PPP,FRAME-RELAY |
802.3 | EIA/TIA-232,V.35 |
在TCP/IP参考模型中,去掉了OSI参考模型中会话层和表示层(这两层的功能被合并到应用层实现),同时将OSI参考模型中的数据链路层和物理层合并为主机联网层。
2.简单的本地通信
使用套接字除了可以实现网络间不同主机间的通信外,还可以实现同一主机的不同进程间的通信,且建立的通信是双向的对等通信。
socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。socket编程要考虑服务器端和客户端两方面内容,而客户端/服务器模式也是网络上绝大多数通信应用程序都使用的机制。客户服务器模式要求每个应用程序由两部分组成,一部分负责启动通信,另一部分负责应答。客户端和服务端关系如图。
2.1 socket实现本地通信
服务器端的编程步骤:
(1)调用socket函数来创建一个socket描述符。
(2)准备通信地址。
(3)对通信地址和socket描述符进行绑定(使用bind函数)
(4)读写数据(使用read和write函数实现)
(5)关闭socket描述符(使用close函数)
客户端的编程步骤:
(1)调用socket函数来创建一个socket描述符。
(2)准备通信地址
(3)对通信地址和socket描述符进行连接(使用socket函数)
(4)读写数据(使用read和write函数实现)
(5)关闭socket描述符(使用close函数)
我们发现,客户端和服务端除了第(3)步不同,其他步骤基本相同。
2.2 相关API讲解
下面详细介绍编程步骤里提到的各个函数以及通信地址(实质上是定义一个结构体类型的变量)的用法
1.socket函数
函数原型:int socket (int domain, int type, int protocol);
参数domain的取值及作用:
AF_UNIX/AF_LOCAL/AF_FILE: 创建本地通信描述符。
AF_INET:创建网络通信描述符IPV4。
AF_INET6: 创建网络通信描述符IPV6。
注:AF换成PF效果一样
参数type的取值及作用:
SOCK_STREAM: 实现面向连接的通信类型,即基于TCP的通信。
SOCK_DGRAM: 实现面向非连接的通信类型,即基于UDP的通信。
参数protocol本来用于指定协议,但目前基本没有意义,所以赋值为0即可。
返回值:成功则返回socket描述符(非负整数),若失败返回值为-1。
2.通信需要准备的结构体
struct sockaddr: 无实际意义的结构体,称之为“傀儡”。
struct sockaddr_un: 表示负责本地通信的地址数据。
struct sockaddr_in:表示负责网络通信的地址数据。
#include <sys/un.h>
#include <netinet/in.h>
struct sockaddr_un
{
sa_family_t sun_family; // 用于指定协议簇,要和创建socket描述符步骤中socket函数参数domain的取值一致
char sun_path[]; //存放socket文件名(只要是存在的一个文件或文件夹即可,一般情况下使用特殊目录.或者..)
};
struct sockaddr_in
{
sa_family_t sin_family; // 用于指定协议簇,要和创建socket描述符步骤中socket函数参数domain的取值一致
short sin_port; // 端口号
struct in_addr sin_addr; // 存储IP地址
};
3.bind函数
函数原型: int bind(int sockfd, struct sockadd *addr, socklen_size);
参数说明:
sockfd:编程步骤(1)中使用socket函数返回的描述符。
addr: 通信使用的地址,本质是sockaddr_in或sockaddr_un类型的数据,使用时将作类型转换,转换成struct sockaddr * 类型的数据。
size: addr对应结构体的大小。
返回值:成功则返回0,失败则返回-1。
4.connect函数
函数原型: int connect(SOCKET s,const struct sockaddr * name,int namelen);
参数说明:
s: socket描述符。
name: 指向要连接套接字的sockaddr结构体的指针。
namelen: sockaddr结构体的字节长度。
返回值:成功返回0,失败返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取响应错误代码。
5.read和write函数
函数原型:int read(int fd, char *buf,int len);
int write(int fd, char *buf, int len);
参数说明:
fd指定读写操作的socket描述符。
buf在read函数中指定接收数据缓冲区,在write函数中表示发送数据缓冲区。
len 指定接收或发送的数据大小。
返回值:read函数执行成功后返回读到的数据大小,失败则返回-1;write函数执行成功后返回写入的数据大小,失败则返回-1;
以上就是关于socket实现本地通信所需要的函数。
用socket实现本地通信测试程序。
2.3 服务端和客户端代码演示
local_server.c服务器端程序代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
int main()
{
/* 创建socket描述符 */
int sockfd = socket(PF_UNIX,SOCK_DGRAM,0); // UDP通信
if(sockfd == -1)
{
perror("socket failed");
exit(-1);
}
/* 准备地址 */
struct sockaddr_un addr;
addr.sun_family = PF_UNIX;
strcpy(addr.sun_path, "a.sock");
/* 绑定 */
int res = bind(sockfd,(struct sockaddr *)&addr, sizeof(addr)); //做强制类型转换
if(res == -1)
{
perror("bind failed!");
exit(-1);
}
/* 进行通信 */
char buf[100] = {0};
int len = read(sockfd,buf,sizeof(buf));
if(len < 0)
{
perror("read failed ");
exit(-1);
}
printf("read from sockfd %s\n", buf);
/* 关闭socket描述符 */
close(sockfd);
/* 删除a.sock*/
unlink("a.sock");
return 0;
}
local_client.c客户端程序代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
int main()
{
/* 创建socket描述符 */
int sockfd = socket(PF_UNIX,SOCK_DGRAM,0);
if(sockfd == -1)
{
perror("socket failed ");
exit(-1);
}
/* 准备地址 */
struct sockaddr_un addr;
addr.sun_family = PF_UNIX;
strcpy(addr.sun_path,"a.sock");
/* 进行连接 */
int res = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));
if(res == -1)
{
perror("connect failed!");
exit(-1);
}
/* 进行通信 */
int len = write(sockfd," hello socket",13);
if(len < 0)
{
perror("write failed");
exit(-1);
}
/* 关闭 */
close(sockfd);
/* 删除a.sock */
unlink("a.sock");
return 0;
}
执行命令编译两个源程序
gcc local_server.c -o local_server
gcc local_client.c -o local_client
先执行服务器端程序,再执行客户端程序,结果如下图