目录
0. 准备知识
0.1 大小端概念
0.2 网络字节序和主机字节序的转换
0.3 点分十进制串转换(IP地址转换函数)
0.4 IPV4结构体:(man 7 ip)
0.5 IPV6套接字结构体:(man 7 ipv6)
0.6 通用套接字结构体
1. 网络套接字函数
1.1 socket
1.2 connect
1.3 bind
1.4 listen
1.5 accept
1.6 端口复用
2. 包裹函数
2.1 wrap.c
2.2 wrap.h
3.TCP服务器
3.1 简单版
3.2 多进程版
3.3 多线程版
4. UDP服务器
5. 本地套接字
总结:
0. 准备知识
0.1 大小端概念
大端存储模式
:是指数据的低位字节序保存在内存的高地址中,而数据的高位字节序保存在内存的低地址中小端存储模式
:是指数据的低位字节序保存在内存的低地址中,而数据的高位字节序保存在内存的高地址中
当以不同的存储方式,存储数据为0x12345678时:
0.2 网络字节序和主机字节序的转换
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。如果主机是大端字节序的,发送和接收都不需要做转换。同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostlshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network,l表示32位,s表示16位。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
代码示例1:
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
char buf[4] = {
192, 168, 1, 2
};
unsigned int num = *(int*)buf;
unsigned int sum = htonl(num);
unsigned char* p = (unsigned char*)∑
printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));
unsigned short a = 0x0102;
unsigned short b = htons(a);
printf("%#x\n", b);
return 0;
}
执行截图:
代码示例2:
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
unsigned char buf[4] = {
1, 1, 168, 192
};
int num = *(int*)buf;
int sum = ntohl(num);
unsigned char* p = (unsigned char*)∑
printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));
return 0;
}
执行截图:
0.3 点分十进制串转换(IP地址转换函数)
我们通常见到的ip地址是字符串“192.168.1.2”这种类型的,需要进行转换才行。
#include <apra/inet.h>
//将点分十进制串转换成32位网络大端的数据
int inet_pton(int af, const char *src, void *dst);
支持IPV4和IPV6
参数:
af:
AF_INET:IPV4
AF_INET6:IPV6
src:点分十进制串的地址
dst:存储32位网络数据的地址
返回值:
成功:1
失败:0
#include <apra/inet.h>
//将32位网络大端的数据转换成点分十进制串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数:
af:
AF_INET:IPV4
AF_INET6:IPV6
src:32位网络数据的地址
dst:存储点分十进制串的地址
size:存储点分十进制串数组的大小(位数要具体决定)
INET_ADDRSTRLEN 宏的值 16
返回值:
存储点分十进制串的首地址
代码案例:
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
char buf[] = "192.168.1.2";
unsigned int num = 0;
inet_pton(AF_INET, buf, &num);
unsigned char* p = (unsigned char*)#
printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));
char ip[16] = "";
inet_ntop(AF_INET, &num, ip, 16);
printf("%s\n", ip);
return 0;
}
执行截图:
网络通讯解决三大问题:协议,IP,端口
0.4 IPV4结构体:(man 7 ip)
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
0.5 IPV6套接字结构体:(man 7 ipv6)
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
0.6 通用套接字结构体
struct sockaddr {
sa_family_t sa_family; // 地址族
char sa_data[14]; // 地址数据
};
注意:通常用以下形式
struct sockaddr_in addr;
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
1. 网络套接字函数
1.1 socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type :
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol :返回值:
成功:文件描述符
失败:-1
1.2 connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:连接服务器
参数:
sockfd:socket文件描述符
addr:ipv4套接字结构体的地址,含IP地址和端口号
addrlen:ipv4套接字结构体的长度
返回值:
成功:0
失败:-1
1.3 bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:bind绑定
参数:
sockfd:socket文件描述符
addr:ipv4套接字结构体,含IP地址和端口号
addrlen:ipv4套接字结构体的大小
返回值:
成功:0
失败:-1
1.4 listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:listen监听
参数:
sockfd:socket文件描述符
backlog:已完成队列和未完成队列里数之和的最大值 128
查看:cat /proc/sys/net/ipv4/tcp_max_syn_backlog
返回值:
成功:0
失败:-1
1.5 accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:从已完成连接队列提取新的连接(如果没有新的连接,accept会阻塞)
参数:
sockfd:套接字
addr:ipv4套接字结构体
addrlen:ipv4套接字结构体的大小的地址
返回值:
成功:新的已连接套接字的文件描述符
失败:-1
1.6 端口复用
在server代码的socket和bind调用之间插入如下代码:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
注意:程序中设置某个端口重新使用,在这个之前的其他网络程序将不能使用这个端口
2. 包裹函数
2.1 wrap.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
void perr_exit(const char* s)
{
perror(s);
exit(-1);
}
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
int n;
again:
if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))//如果是被信号中断和软件层次中断,不能退出
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
int n;
if ((n = connect(fd, sa, salen)) < 0)
perr_exit("connect error");
return n;
}
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
int Socket(int family, int type, int protocol)
{
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void* ptr, size_t nbytes)
{
ssize_t n;
again:
if ((n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)//如果是被信号中断,不应该退出
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
ssize_t n;
again:
if ((n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
int Close(int fd)
{
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
/*参三: 应该读取固定的字节数数据*/
ssize_t Readn(int fd, void* vptr, size_t n)
{
size_t nleft; //usigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
char* ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;
else
return -1;
}
else if (nread == 0)
break;
nleft -= nread;
ptr += nread;
}
return n - nleft;
}
/*:固定的字节数数据*/
ssize_t Writen(int fd, const void* vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char* ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd, char* ptr)
{
static int read_cnt;
static char* read_ptr;
static char read_buf[100];
if (read_cnt <= 0) {
again:
if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
}
else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
ssize_t Readline(int fd, void* vptr, size_t maxlen)
{
ssize_t n, rc;
char c, * ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ((rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
}
else if (rc == 0) {
*ptr = 0;
return n - 1;
}
else
return -1;
}
*ptr = 0;
return n;
}
int tcp4bind(short port, const char* IP)
{
struct sockaddr_in serv_addr;
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
if (IP == NULL) {
//如果这样使用 0.0.0.0,任意ip将可以连接
serv_addr.sin_addr.s_addr = INADDR_ANY;
}
else {
if (inet_pton(AF_INET, IP, &serv_addr.sin_addr.s_addr) <= 0) {
perror(IP);//转换失败
exit(1);
}
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
//端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
return lfd;
}
2.2 wrap.h
#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
void perr_exit(const char* s);
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
int Bind(int fd, const struct sockaddr* sa, socklen_t salen);
int Connect(int fd, const struct sockaddr* sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
ssize_t my_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);
int tcp4bind(short port, const char* IP);
#endif
3.TCP服务器
socket模型创建流程图:
3.1 简单版
client.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERVER_IP "192.168.0.105"
#define SERVER_PORT 8008
int main()
{
//创建套接字
int sock_fd;
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
//连接服务器
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, SERVER_IP, &addr.sin_addr);
connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
//读写数据
char buf[1024] = "";
while (1)
{
int n = read(STDIN_FILENO, buf, sizeof(buf));
write(sock_fd, buf, n);//发送数据
n = read(sock_fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);
}
//关闭
close(sock_fd);
return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERVER_PORT 8008
#define SERVER_IP "192.168.0.106"
#define BACKLOG 128
int main()
{
//创建套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
//绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
//addr.sin_addr.s_addr = INADDR_ANY; //绑定的是通配地址
inet_pton(AF_INET, SERVER_IP, &addr.sin_addr.s_addr);
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
//监听
listen(lfd, BACKLOG);
//提取
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
char ip[16] = "";
printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
//读写
char buf[1024] = "";
while (1)
{
bzero(buf, sizeof(buf));
int n = read(STDIN_FILENO, buf, sizeof(buf));
write(cfd, buf, n);
n = read(cfd, buf, sizeof(buf));
printf("%s\n", buf);
}
//关闭
close(lfd);
close(cfd);
return 0;
}
客户端和服务器启动后可以使用netstat命令查看链接情况:
netstat -apn|grep 6666
3.2 多进程版
server.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include "wrap.h"
#define SERVER_PORT 8000
#define SERVER_IP "192.168.0.106"
#define BACKLOG 128
void free_process(int sig)
{
pid_t pid;
while ((pid = waitpid(-1, NULL, WNOHANG)) > 0)
{
printf("child pid = %d has exited\n", pid);
}
}
void handle_client(int cfd)
{
char buf[1024];
ssize_t n;
while ((n = read(cfd, buf, sizeof(buf))) > 0)
{
printf("from clent :%s\n", buf);
if (write(cfd, buf, n) < 0)
{
perror("Fail to sedn response to client");
Close(cfd);
exit(1);
}
}
if (n < 0)
{
perror("Fail to read from client");
}
printf("Client closed connection\n");
Close(cfd);
exit(0);
}
int main()
{
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = free_process;
if (sigaction(SIGCHLD, &act, NULL) < 0)
{
perror("fail to sigaction");
exit(1);
}
//创建套接字
int lfd = tcp4bind(SERVER_PORT, NULL);
//监听
Listen(lfd, BACKLOG);
//提前
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
while (1)
{
char ip[16] = "";
//提取连接
int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
//fork创建子进程
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fail to fork");
Close(cfd);
continue;
}
else if (pid == 0)
{
Close(lfd);
handle_client(cfd);
break;
}
Close(cfd);
}
//关闭
Close(lfd);
return 0;
}
3.3 多线程版
server.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include "wrap.h"
typedef struct c_info {
int cfd;
struct sockaddr_in cliaddr;
}CINFO;
void* client_fun(void* arg);
int main(int argc, char* argv[])
{
if (argc < 2)
{
printf("argc < 2\n ./a.out 8000 \n");
return 0;
}
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
short port = atoi(argv[1]);
int lfd = tcp4bind(port, NULL);
Listen(lfd, 128);
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
CINFO* info;
while (1)
{
int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
char ip[16] = "";
pthread_t pthid;
info = (CINFO*)malloc(sizeof(CINFO));
info->cfd = cfd;
info->cliaddr = cliaddr;
pthread_create(&pthid, &attr, client_fun, info);
}
return 0;
}
void* client_fun(void* arg)
{
CINFO* info = (CINFO*)arg;
char ip[16] = "";
printf("new client ip =%s port =%d\n", inet_ntop(AF_INET, &(info->cliaddr.sin_addr.s_addr), ip, 16), ntohs(info->cliaddr.sin_port));
while (1)
{
char buf[1024] = "";
int count = 0;
count = read(info->cfd, buf, sizeof(buf));
if (count < 0)
{
perror("");
break;
}
else if (count == 0)
{
printf("client close\n");
break;
}
else
{
printf("%s\n", buf);
write(info->cfd, buf, count);
}
}
close(info->cfd);
free(info);
pthread_exit(NULL);
}
4. UDP服务器
相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。
由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。
与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:
- 服务器应用层设计流量控制,控制发送数据速度。
- 借助setsockopt函数改变接收缓冲区大小。如:
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
server.c
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>
#define MAXLINE 80
#define SERV_PORT 8080
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int sockfd, i, n;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
return 1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
return 1;
}
printf("Accepting connections ...\n");
while (1) {
n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr*)&cliaddr, &cliaddr_len);
if (n < 0) {
perror("recvfrom error");
continue;
}
printf("Received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper((unsigned char) buf[i]);
if (sendto(sockfd, buf, n, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr)) < 0)
perror("sendto error");
}
if (close(sockfd) < 0) {
perror("close error");
return 1;
}
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#define MAXLINE 80
#define SERV_PORT 8080
int main(int argc, char* argv[])
{
struct sockaddr_in servaddr;
int sockfd, n;
char buf[MAXLINE];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket creation failed");
return 1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
while (fgets(buf, MAXLINE, stdin) != NULL) {
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (n < 0) {
perror("sendto error");
continue;
}
n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
if (n < 0) {
perror("recvfrom error");
continue;
}
if (write(STDOUT_FILENO, buf, n) < 0) {
perror("write error");
}
}
if (close(sockfd) < 0) {
perror("close error");
return 1;
}
return 0;
}
5. 本地套接字
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIXDomain Socket通讯的。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。
UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
对比网络套接字地址结构和本地套接字地址结构:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ 地址结构类型
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址
};
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型
char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径)
};
以下程序将UNIX Domain socket绑定到一个地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->member)
service:
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#define QLEN 10
/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
*/
int serv_listen(const char* name)
{
int fd, len, err, rval;
struct sockaddr_un un;
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
/* in case it already exists */
unlink(name);
/* fill in socket address structure */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr*)&un, len) < 0) {
rval = -2;
goto errout;
}
if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
rval = -3;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(rval);
}
int serv_accept(int listenfd, uid_t* uidptr)
{
int clifd, len, err, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr*)&un, &len)) < 0)
return(-1); /* often errno=EINTR, if signal caught */
/* obtain the client's uid from its calling address */
len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
un.sun_path[len] = 0; /* null terminate */
if (stat(un.sun_path, &statbuf) < 0) {
rval = -2;
goto errout;
}
if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -3; /* not a socket */
goto errout;
}
if (uidptr != NULL)
*uidptr = statbuf.st_uid; /* return uid of caller */
/* we're done with pathname now */
unlink(un.sun_path);
return(clifd);
errout:
err = errno;
close(clifd);
errno = err;
return(rval);
}
int main(void)
{
int lfd, cfd, n, i;
uid_t cuid;
char buf[1024];
lfd = serv_listen("foo.socket");
if (lfd < 0) {
switch (lfd) {
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}
cfd = serv_accept(lfd, &cuid);
if (cfd < 0) {
switch (cfd) {
case -3:perror("not a socket"); break;
case -2:perror("a bad filename"); break;
case -1:perror("accept"); break;
}
exit(-1);
}
while (1) {
r_again:
n = read(cfd, buf, 1024);
if (n == -1) {
if (errno == EINTR)
goto r_again;
}
else if (n == 0) {
printf("the other side has been closed.\n");
break;
}
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, n);
}
close(cfd);
close(lfd);
return 0;
}
client:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
/*
* Create a client endpoint and connect to a server.
* Returns fd if all OK, <0 on error.
*/
int cli_conn(const char* name)
{
int fd, len, err, rval;
struct sockaddr_un un;
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
/* fill socket address structure with our address */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
/* in case it already exists */
unlink(un.sun_path);
if (bind(fd, (struct sockaddr*)&un, len) < 0) {
rval = -2;
goto errout;
}
/* fill socket address structure with server's address */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
if (connect(fd, (struct sockaddr*)&un, len) < 0) {
rval = -4;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(rval);
}
int main(void)
{
int fd, n;
char buf[1024];
fd = cli_conn("foo.socket");
if (fd < 0) {
switch (fd) {
case -4:perror("connect"); break;
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}
while (fgets(buf, sizeof(buf), stdin) != NULL) {
write(fd, buf, strlen(buf));
n = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);
}
close(fd);
return 0;
}
总结:
这些都是 C语言实现的代码,建议理解并自行敲出来。