对于网络通信,特别是TCP/IP协议栈,Linux提供了socket作为应用层与网络层之间的接口。socket是网络通信的端点,它封装了内部的网络通信协议细节,使得应用程序无需关心底层通信的具体实现,只需要通过统一的系统调用来与socket进行交互,就可以实现与远程主机的通信。
Socket API的使用示例
socket提供了以下系统调用:
- socket()指明网络通信的类型,创建一个新的socket。
- bind()绑定一个socket到指定的地址和端口。
- connect()发起一个连接请求到指定的服务器地址和端口。
- send()发送数据。
- recv()接收数据。
- ......
示例代码如下:
/* 服务器 */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
int main()
{
// socket
int fd = socket(AF_INET, SOCK_STREAM, 0); // IPv4, TCP
// bind
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(8000); // 端口号
inet_pton(AF_INET, "0.0.0.0", &server.sin_addr); // IP地址
bind(fd, (struct sockaddr*)&server, sizeof(server));
// listen
listen(fd, 10);
while (1) {
// accept
int connect_fd = accept(fd, NULL, NULL);
if(fork() == 0) { // 子进程
// recv
char buf[4096];
int len = recv(connect_fd, buf, sizeof(buf), 0);
buf[len] = '\0';
printf("客户端: %s", buf);
// send
char msg[] = "我在,有什么事尽管吩咐。\n";
send(connect_fd, msg, sizeof(msg), 0);
// close
close(connect_fd);
exit(0);
}
close(connect_fd);
}
}
/* 客户端 */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
int main()
{
// socket
int fd = socket(AF_INET, SOCK_STREAM, 0); // IPv4, TCP
// connect
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(8000); // 端口号
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr); // IP地址
connect(fd, (struct sockaddr*)&server, sizeof(server));
// send
char msg[] = "服务器,在吗?\n";
send(fd, msg, sizeof(msg), 0);
// recv
char buf[4096];
int len = recv(fd, buf, sizeof(buf), 0);
buf[len] = '\0';
printf("服务器: %s",buf);
// close
close(fd);
}
上述代码的运行结果如下:
上述代码演示了如何使用socket API进行TCP通信。如果使用IPv6地址,或者使用UDP通信,或者使用专门针对本地进程间通信的socket,代码会略有不同,但是使用的系统调用是一样的。
Socket系统调用的实现
/linux-2.6.34/net/socket.c里面是socket提供的系统调用。socket本身并不直接负责网络通信,而是接收用户的参数,并根据这些参数调用内核中相应的网络协议栈函数来执行实际的网络通信,下面以bind()这个系统调用为例:
结构体socket只包含了网络通信的一些通用属性,与具体网络协议相关的属性在结构体sock里面:
结构体proto_ops声明了具体网络协议需要实现的操作:
在调用socket()时,需要指定协议族和通信类型,这些参数决定了使用的网络协议。在创建socket对象时,与具体网络协议相关的操作的函数指针会被放到socket对象上面(相关代码位于__sock_create()函数)。
Socket文件的实现
在Linux系统中,几乎所有的I/O资源都被抽象为文件,网络通信也不例外,因此/linux-2.6.34/net/socket.c需要和文件系统打交道。
socket(网络通信的特有属性)需要和inode(I/O资源的一般属性)绑定在一起:
当创建socket对象时,也同时需要创建一个inode对象(相关代码位于sock_alloc_inode()函数),以及创建一个file对象(相关代码位于sock_alloc_file()函数)。
结构体file_operations里面声明的文件操作需要被实现:
有的文件操作与具体网络协议无关,在socket.c里就可以完成(sock_close()函数和sock_fasync()函数),其它文件操作则必须要调用具体的网络协议来完成。
总结
/linux-2.6.34/net/socket.c负责向应用程序提供网络通信的系统调用,从而使得应用程序可以以一种统一的方式与各种网络协议进行通信。/linux-2.6.34/net/socket.c本身只负责一些与具体网络协议无关的工作,其中,最大的工作就是将网络通信与文件系统关联起来。