Linux 系统编程学习笔记 - socket编程
预备知识
socket概念
socket可以表示很多概念:
- 在TCP/IP协议中,“IP地址 + TCP/UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”称为socket。
- 在TCP协议中,建立连接的2个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述连接的一对一关系。
- TCP/IP协议最早在BSD UNIX上实现,为TCP/IP设计的应用层编程接口称为socket API。
网络编程中的socket, 通常指socket API (socket, bind, listen, accept, connect等)。
网络字节序
内存中多字节数据相对于内存地址,有大小端之分,网络数据流也有。
先看下什么是大端、小端?
详解大端模式和小端模式 | 博客园
以存放0x12345678为例,说明大端、小端的区别:
- 大端 big endian:高位字节存放在内存的低地址端,低位字节存放在内存的高地址端
0x12345678本身 => 高位字节:0x12,低位字节:0x78 ,高位、低位字节取决于数据本身
低地址 --------------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
- 小端 little endian:低位字节存放在内存的低地址端,高位字节存放在内存的高地址端
0x12345678本身 => 高位字节:0x12,低位字节:0x78
低地址 --------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
网络数据流的地址
发送主机通常将发送缓冲区中的数据,按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存.
把地址的高低, 与字节序号的高低一一对应, 即地址低 <=> 字节序号低, 地址高 <=> 字节序号高.
网络数据流的大端小端是什么?
TCP/IP协议规定,网络数据流的采用大端字节序,即低地址高(位)字节。
下图是UDP协议数据包格式示意图:
下图以发送源端口号=0x3e8为例, 说明网络数据流的大端, 小端直接区别:
大端, 小端如何转换?
网络字节序是网络协议规定的, 而存储字节序取决于硬件体系结构. 它们如何转换?
如果发送和接收主机是大端字节序,就不需要转换;如果任何一端是小端字节序,就需要转换. 可以调用以下库函数做网络字节序和主机字节序转换(为了使网络程序具备可移植性,建议使用).
注: IP地址, 端口号可以利用下面的库函数转换.
#include <arpa/inet.h>
// h - host, n - network
// l - 32bit long, s - 16bit short
// 如果主机是小端字节序,将参数做相应的大小端转换,然后返回;
// 如果主机是大端字节序,参数直接返回
uint32_t htonl(uint32_t hostlong); // 32bit unsigned long主机序 -> 网络字节序
uint16_t htons(uit16_t hostshort); // 16bit unsigned short主机序 -> 网络字节序
uint32_t ntohl(uint32_t netlong); // 32bit unsigned short网络字节序 -> 主机序
uint16_t ntohs(uint16_t netshort); // 16bit unsigned short网络字节序 -> 主机序
socket地址的数据类型及相关函数
socket API是一层抽象的网络编程接口,适用于各种底层网络协议(如Ipv4、Ipv6及UNIX Domain Socket)。然而各种网络协议的地址格式并不相同:
IPv4地址格式 , 地址类型; IPv6地址格式 struct sockaddr_in6; UNIX Domain Socket地址格式 struct sockaddr_un
地址格式(socket addr类型) | 地址类型/协议族(sa_family_t) | 备注 | |
---|---|---|---|
IPv4 | struct sockaddr_in | AF_INET | 用于IPv4通信 |
IPv6 | struct sockaddr_in6 | AF_INET6 | 用于IPv6通信 |
Unix Domain Socket | struct sockaddr_un | AF_INET6 | 用于Unix Domain Socket通信 |
注: 头文件 netinet/in.h |
sockaddr数据结构示意图:
问题:从上面的表格知道, 可以从结构体类型上区分是使用IPv4, IPv6 or Unix Domain Socket,为何还要多添加一个字段sa_family_t用于表示地址类型呢?
一种地址类型(协议族)只有一种地址格式与之对应, 如AF_INET表示IPv4通信, 只有struct sockaddr_in对应的IPv4数据报文格式, 才能与之对应. 为了统一接口, 无论IPv4, IPv6, 还是Unixi Domain Socket, 都使用通用的struct sockaddr格式, 这样socket API可以接受各种类型的sockaddr结构体指针做参数,而不需要知道具体是哪种类型, 例如bind, accept, connect等函数的参数可以用同一种类型struct sockaddr*来表示地址格式. 具体的哪种类型的地址, 就用地址类型来区分.
传递参数示例(需要强制类型转换):
struct sockaddr_in sockaddr; // IPv4地址格式, 包含了IP地址 + 端口信息
... // 设置sockaddr的IP地址, 端口. 发送数据注意使用网络序, 接收数据使用主机序
// 绑定套接字与IP地址 + 端口信息
// servaddr对应实参可能是struct sockaddr_in, struct sockaddr_in6, 或struct sockaddr_un
// 如果使用具体的地址格式类型, 有多少种不同协议地址类型, 就需多少种接口
bind(listen_fd, (struct_sockaddr *)&sockaddr, sizeof(sockaddr));
IP地址转换 字符串 <-> 32bit数据
IPv4的sockaddr_in的成员sin_addr (struct in_addr)表示32位IP地址, 常需要转化成点分十进制的字符串IP地址(const char *类型).
点分十进制字符串 -> in_addr:
#include <arpa/inet.h>
/* strptr 点分十进制IP地址;
* addrptr 32bit ip地址
*/
int inet_aton(const char *strptr, struct in_addr *addrptr);
int_addr inet_addr(const char *strptr);
// 支持转换IPv4的in_addr,也支持IPv6的in6_addr
int inet_pton(int family, const char *strptr, void *addrptr);
in_addr转点分十进制字符串:
char *inet_ntoa(struct in_addr inaddr);
// 支持转换IPv4的in_addr,也支持IPv6的in6_addr
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
基于TCP协议的网络程序
TCP client/server程序一般流程:
时序图:
连接建立的程序过程
- 服务器调用socket(), bind(), listen() 进行初始化;
- 服务器调用accept()阻塞等待,监听端口状态;
- 客户端调用socket()初始化;
- 客户端调用connect()发送SYN段,并阻塞等待服务器应答;
- 服务器收到SYN段后,应答一个SYN-ACK段;
- 客户端收到服务器的SYN-ACK段后,从已阻塞的connect()返回,同时应答一个ACK段;
- 服务器收到客户端应答的ACK段后,从已阻塞的accept返回。
数据传输的过程
- 服务器从accept()返回后立刻调用read(),读socket,如果没有数据就阻塞等待;
- 客户端调用write()发送请求给服务器,服务器收到后从read()返回,并对客户端的请求进行处理;
- 客户端write()之后,调用read()阻塞等待服务器的应答;
- 服务器wirte()之后,调用read()阻塞等待下一条请求,客户端读取后从read()返回,发送下一条请求(跳到2),这样不断循环。
连接的断开过程
- 如果客户端没有更多请求了,调用close()关闭连接;
- 服务器从read()返回0,就得知客户端关闭了连接,也调用close()关闭连接。
注意:任何一方调用close(),连接的两个传输方向都关闭,不能再发送数据。如果一方调用shutdown则连接处于半关闭状态,仍可接收对方发来的数据。
最简单的TCP网络程序
完整代码参见: linuxstudy 文件名:server.c, client.c
server端
例,server.c的作用是从client读字符,然后将每个字符转换为大写并回送到客户端。
// server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main() {
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; // 16
int i, n;
// step1 socket() 初始化socket,获取文件描述符
// 类似于打开一个通道, listenfd就是这个通道的文件描述符
listenfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET - IPv4 IP地址;AF_INET6 - IPv6 IP地址;AF_UNIX - Unix Domain Socket
// 初始化sockaddr, 设置socket IP地址+端口信息
bzero(&servaddr, sizeof(servaddr)); // 清空sockaddr
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 将32位主机序 IP地址INADDR_ANY 转化成网络序IP地址
servaddr.sin_port = htons(SERV_PORT); // 将16位主机序 端口号SERV_PORT 转换成网络序端口号
/*
思考:这里为什么要用htonl/htons转换IP地址和端口?为什么不转地址类型AF_INET?
因为地址类型AF_INET是用于程序判断IP地址类型的,而IP地址和端口是要用网卡发送到网络上的数据,为了确保程序可移植性,需要转换成网络字节序
*/
// step2 bind() 绑定socket和IP地址
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 注意bind第二个参数接收的是struct sockaddr *类型,而变量sockaddr是sockaddr_in类型
// step3 listen() 设置监听队列
listen(listenfd, 20); // 处于监听状态的的流套接字listenfd, 将维护一个客户请求队列, 最多容纳20个用户请求
printf("Accepting connections...\n");
while(true) {
cliaddr_len = sizeof(cliaddr);
// step4 accept() 阻塞等待客户端连接请求
connfd = accept(listenfd, &cliaddr, &cliaddr_len); // 阻塞等待客户连接请求(SYN段),如果从accept成功返回表明已经收到客户端SYN段
// step5 read()/write() 读取客户端数据或向客户端发送数据
// 为什么是从accept返回的connfd读取,而不是socket返回的listenfd?因为accept绑定的是客户端,listenfd绑定的是服务器端(也就是自身)的IP地址及端口信息
n = read(connfd, buf, MAXLINE); // 从监听端口读取n byte数据,存储到buf[0..n-1]
printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), // 将k客户端IP地址信息in_addr转换成字符串str,
ntohs(cliaddr.sin_port)); // 将客户端端口号由主机字节序转化成网络字节序
for (i = 0; i < n; ++i) {
buf[i] = toupper(buf[i]); // buf[0..n-1]转换成大写
}
write(connfd, buf, n); // 写回给客户端
// step6 close() 发送FIN段,关闭TCP连接
close(connfd);
}
return 0;
}
socket(), bind(), listen(), accept(), read(), write() 这几个socket API都位于头文件sys/socket.h中。
- socket()
socket()打开一个网络通讯端口,如果成功,就像open一样返回一个文件描述符,可以通过该文件描述符绑定IP地址、监听端口、与客户端建立连接。
要真正让APP可以像读写文件一样read()/write()在网络上收发数据,还需要绑定IP地址及端口,还有和客户端建立连接之后。
#include <sys/socket.h>
#include <sys/types.h>
// 成功返回一个文件描述符;出错返回-1
// family 用于指示协议族的名字,AF_INET为IPv4,AF_INET6为IPv6,AF_UNIX为Unix Domain Socket
// type 指示类型,SOCK_STREAM表示TCP协议,SOCK_DGRAM表示UDP协议
// protocol 用于指示对于这种socket的具体协议类型。一般情况下,指定了前2个参数就,如果只存在一种协议类型对应该情况,就可以将protocol设置为0;某些情况下,会存在多个协议类型时,就必须指定具体的协议类型。
int socket(int family, int type, int protocol);
SOCK_STREAM(流) : 提供有序,可靠的双向连接字节流。 可以支持带外数据传输机制,无论多大的数据都不会截断;
SOCK_DGRAM(数据报):支持数据报(固定最大长度的无连接,不可靠的消息),数据报超过最大长度,会被截断;
- bind()
服务器监听的网络地址和端口号通常是固定不变的(通常是知名服务, 如Telnet/STMP等, 对应着知名端口号), 客户端程序得知服务器程序的地址和端口号后,可以向服务器发起连接请求.
服务器调用bind绑定一个固定的网络地址和端口号; 客户端IP和端口不固定,不需要bind绑定端口, 也就是不需要调用bind.
// 绑定sockfd和myaddr
// addrlen 绑定地址从长度
// 为什么需要第3个参数addrlen?
// 事实上,myaddr可以接受多种协议的sockaddr结构体,而它们参数各不相同,所以需要addrlen指定结构体长度
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
程序中为什么要将地址类型设为INADDR_ANY?
因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以监听所有本地IP地址,直到与某个客户端建立了连接才确定到底用哪个IP地址
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY转换过来, 0.0.0.0, 泛指本机IP地址, 可以使用一套代码同时监听多个网卡
servaddr.sin_port = htons(SERV_PORT); // SERV_PORT = 8000
- listen()
服务器一般可以服务多个客户端,如果有大量客户端同时发起连接,服务器可能会来不及处理,尚未accept处理的客户端处于等待状态。listen可以让服务器为sockfd维护一个队列,监听客户端连接请求。
// 监听端口
// sockfd 用socket()成功创建的TCP套接字(文件描述符)
// backlog 监听队列的大小,即最多容许backlog个客户端处于等待连接状态
// 成功返回0,失败-1
int listen(int sockfd, int backlog);
- accept()
accept用于从指定套接字的连接队列中取出第一个连接,并返回一个新的套接字用于与客户端通信。
accept会一直阻塞,直到有客户端成功连接(已完成三次握手)
客户端是连接的请求方,而不是接受方,不需要调用accept。
// sockaddr 处于监听状态的套接字
// cliaddr 用于保存客户端的地址信息
// addrlen_t [in][out] 传入传出参数,传入值是cliaddr缓存的大小以避免溢出,传出值是客户端地址结构体的实际长度。 NULL表示不关心客户端的地址
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen_t);
服务器程序结构:
while(true) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
...
close();
}
这里的read, write跟文件IO里面的读写操作,是同一个IO接口,位于头文件unistd.h
client端
client.c从命令行获取一个字符串,发给服务器,然后接收服务器返回的字符串并打印。
// client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[]) {
struct socketaddr_in sockaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if (argc != 2) {
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1]; // 要传给服务器的字符串
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&sockaddr, sizeof sockaddr);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &sockaddr.sin_addr);
connect(sockfd, (struct sockaddr *)&sockaddr, sizeof sockaddr);
write(sockfd, str, strlen(str));
n = read(sockfd, buf, MAXLINE);
printf("Response from Server:\n");
write(STDOUT_FILENO, buf, n);
close(sockfd);
return 0;
}
先编译、运行服务器:
$ gcc server.c -o server
$ ./server
查看服务器端口占用情况:
$ netstat -apn|grep 8000
服务器占用8000端口,但IP地址还未确定
另开终端,编译运行客户端:
$ gcc client.c -o client
$ ./client abcd
Response from Server:
ABCD
回到server终端,可以看到server输出:
$ ./server
Accepting connections...
received from 127.0.0.1 at PORT xxx
FAQ
如何处理多个client请求?
不同client连接请求,可以用listen设置监听队列大小来缓存,但是对于已经连接上的client,如何并发处理?
- 方式一:使用fork并发处理。阻塞IO
每accept一个客户连接请求,就fork一个子进程来负责read/write以及close。
2.方式二:使用select()同时监听多个阻塞文件的文件描述符(套接字),哪个有数据达到就处理哪个,不需要fork和多进程。 相当于IO多路复用。
参考linux select函数解析以及事例 | 知乎及select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET
如何解决client进程未终止,但服务器程序重启,端口被占用的问题?
服务器重启或退出时,会主动关闭连接,服务器发送FIN段给客户端,客户端收到FIN后处于CLOSE_WAIT状态,但client没有终止,也没有关闭socket 描述符(TCP套接字),因此不会发送FIN给服务器,因此server的TCP链接处于FIN_WAIT2状态。
bind error: Address already in use
解决办法:使用setsockopt()设置socket描述符选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。具体来说是在socket()和bind()之间插入如下代码:
int op = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
基于UDP协议的网络程序
典型的UDP通讯流程
UDP和TCP的主要区别:
- UDP是面向消息的,无连接的,连接不可靠,TCP是面向字节流的,要传输数据必须先进行连接(三次握手),连接可靠;
- IP数据报文格式不一样,TCP对应IP报文首部包含了SYN、WIN、ACK、FIN等与连接相关的信息,UDP的IP报文首部则不包含这些;
- 程序上,UDP在创建socket文件描述符后,服务器端只需进行bind IP地址信息,无需listen、accept等与监听、连接有关的操作,客户端也无需connect进行连接请求;
一个简单的UDP服务器和客户端程序示例
server.c
// server.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main() {
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int sockfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; // 16
int i, n;
// socket() choose protocol - IPv4 UDP
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// bind() bind ip info
Bind(sockfd, (struct sockaddr *)&servaddr, sizeof servaddr);
printf("Accepting connections ...\n");
while(true) {
cliaddr_len = sizeof cliaddr;
n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &cliaddr_len);
if (n == -1)
perr_exit("recvfrom error");
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof str), // in_addr to string
ntohs(cliaddr.sin_port)
);
for (i = 0; i < n; ++i) {
buf[i] = toupper(buf[i]);
}
n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, cliaddr_len);
if (n == -1) perr_exit("sendto error");
}
return 0;
}
client.c
// client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main() {
struct sockaddr_in servaddr;
int sockfd, n;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; // 16
socklen_t servaddr_len;
// socket() choose UDP protocol
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
while (fgets(buf, MAXLINE, stdin) != NULL) {
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof servaddr);
if (n == -1) perr_exit("sendto error");
n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
if (n == -1) perr_exit("recvfrom error");
write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
UNIX Domain Socket IPC
参考Linux下进程间通讯方式 - UNIX Domain Socket
服务端: socket -> bind -> listen -> accet -> recv/send -> close
客户端: socket -> connect -> recv/send -> close
当然,客户端也可以bind,自己指定socket文件,便于服务器区分不同的客户端。
服务器端
使用listen设置监听队列时,socket()的type参数不能使用SOCK_DGRAM,需使用SOCK_STREAM。
// server.c
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdbool.h>
#define QLEN 2
#define MAXLINE 80
/**
* create a server endpoint of a connection
* @param name
* @return Returns fd if all OK, <0 on error.
*/
int serv_listen(const char *name) {
int fd, len, err, rval;
struct sockaddr_un un;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return -1;
unlink(name); // remove the special file if it exists
printf("unlink name is %s\n", name);
// fill in socket address structure
memset(&un, 0, sizeof un);
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
printf("server sun_path is %s\n", un.sun_path);
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
// bind the name to descriptor
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
// if (bind(fd, (struct sockaddr *)&un, sizeof un) < 0) {
rval = -2;
goto errout;
}
if (listen(fd, QLEN) < 0) {
printf("serv_listen -3\n");
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;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof un;
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
return -1;
len -= offsetof(struct sockaddr_un, sun_path); // length of pathname
if (stat(un.sun_path, &statbuf) < 0) {
rval = -2;
goto errout;
}
if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -3;
goto errout;
}
if (uidptr != NULL)
*uidptr = statbuf.st_uid;
unlink(un.sun_path);
return clifd;
errout:
err = errno;
close(clifd);
return rval;
}
int main() {
int servfd, clifd;
char *name = "foo.socket";
uid_t uid;
char buf[MAXLINE];
int i;
servfd = serv_listen(name);
if (servfd == -1) {
perror("socket error\n");
exit(1);
}
else if (servfd == -2) {
perror("bind error\n");
exit(2);
}
else if (servfd == -3) {
perror("listen error\n");
exit(3);
}
else printf("listen successfully\n");
for (; ; ) {
clifd = serv_accept(servfd, &uid);
if (clifd < 0) {
perror("serv_accept error");
exit(4);
}
while (true) {
int ret = recv(clifd, buf, MAXLINE, 0);
if (ret < 0) {
printf("recv error\n");
break;
}
printf("received: %s size = %ld\n", buf, strlen(buf));
for (i = 0; i < MAXLINE; ++i) {
buf[i] = toupper(buf[i]);
}
send(clifd, buf, ret, 0);
}
close(clifd);
}
close(servfd);
return 0;
}
客户端
使用bind的socket文件名不能与服务器端的同名(同路径+同文件名),而connect的socket文件名需要与服务器端的socket文件同名;
// client.c
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#define MAXLINE 80
#define CLIA_PATH "/var/tmp/" // + 5 for pid = 14 chars
int cli_conn(const char *name) {
int fd, len, err, rval;
struct sockaddr_un un;
// create a UNIX domain socket
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket error\n");
return -1;
}
memset(&un, 0, sizeof un);
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "%s%05d", CLIA_PATH, getpid());
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
unlink(un.sun_path);
puts(un.sun_path);
// if (bind(fd, (struct sockaddr *)&un, sizeof un)) {
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
perror("bind error\n");
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); // server create socket file with the same
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
if (connect(fd, (struct sockaddr*)&un, len) < 0) {
perror("connect error...\n");
rval = -4;
goto errout;
}
return fd;
errout:
err = errno;
close(fd);
errno = err;
return rval;
}
int main() {
int sockfd;
char* name = "foo.socket";
char buf[MAXLINE];
sockfd= cli_conn(name);
if (sockfd < 0) {
perror("connect error\n");
exit(1);
}
printf("connect successfully\n");
while (fgets(buf, MAXLINE, stdin) != NULL) {
int n = send(sockfd, buf, strlen(buf), 0);
if (n < 0) {
perror("send error\n");
exit(1);
}
else if (n == 0) {
printf("send 0 byte\n");
break;
}
int ret = recv(sockfd, buf,MAXLINE, 0);
write(STDOUT_FILENO, buf, ret);
}
printf("terminate\n");
return 0;
}
参考
《linuxC编程一站式学习》