一:UNIX套接字
用于同一台pc上运行的进程之间通信,它仅仅复制数据,不执行协议处理,不需要增加删除网络报头,无需计算校验和,不产生顺序号,无需发送确认报文。
unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法。
它提供两类套接字,字节流和数据报,分别类似于TCP和UDP。
使用unix域套接字的三个好处:
unix域套接字通常比通信两端位于同一个主机的TCP套接字快出一倍
unix域套接字可用于在同一个主机上的不同进程之间传递描述符
unix能够提供额外的安全检查措施,较新的实现把客户的凭证(用户ID和组ID)提供给服务器
unix域套接字中用于标识客户和服务器的协议地址是普通文件系统中的路径名。
unix域套接字地址结构如下定义:
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family;/* AF_LOCAL */
char sun_path[104];/* 以空字符结尾的字符串 */
}
二:实现流式回射服务器
//服务端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void echo_srv(int conn)
{
char recvbuf[1024];
int n;
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
//不断接收一行数据到recvbuf中
n=read(conn,recvbuf,sizeof(recvbuf));
if(n==-1)
{
if(n==EINTR)
continue;
ERR_EXIT("read");
}
//客户端关闭
else if(n==0)
{
printf("client close\n");
break;
}
fputs(recvbuf,stdout);
write(conn,recvbuf,strlen(recvbuf));
}
close(conn);
}
int main(void)
{
int listenfd;
//创建一个监听套接字
//它的协议家族是PF_UNIX,用流式套接字SOCK_STREAM
if((listenfd=socket(PF_UNIX,SOCK_STREAM,0))<0)
ERR_EXIT("socket");
//unlink表示删除这个文件,先删除,再绑定,重新创造了一个文件
unlink("/tmp/test_socket");
//初始化一个地址绑定监听
struct sockaddr_un servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sun_family=AF_UNIX;
strcpy(servaddr.sun_path,"/tmp/test_socket");
//绑定
//绑定的时候会产生test_socket文件
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind");
//监听
//监听队列的最大值SOMAXCONN
if(listen(listenfd,SOMAXCONN)<0)
ERR_EXIT("listen");
int conn;
pid_t pid;
//接受客户端的连接
while(1)
{
//返回一个已连接套接字
conn=accept(listenfd,NULL,NULL);
if(conn==-1)
{
if(conn==EINTR)
continue;
ERR_EXIT("accept");
}
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
//pid==0(子进程)说明是客户端,执行回射
if(pid==0)
{
//子进程不需要处理监听
close(listenfd);
echo_srv(conn);
exit(EXIT_SUCCESS);
}
//父进程不需要处理连接
close(conn);
}
return 0;
}
//客户端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void echo_cli(int sock)
{
char sendbuf[1024]={0};
char recvbuf[1024]={0};
//不停地从标准输入获取一行数据到一个缓冲区当中
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
//发送给服务器端
write(sock,sendbuf,strlen(sendbuf));
//接收回来
read(sock,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock;
if((sock=socket(PF_UNIX,SOCK_STREAM,0))<0)
ERR_EXIT("socket");
struct sockaddr_un servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sun_family=AF_UNIX;
strcpy(servaddr.sun_path,"/tmp/test_socket");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect");
//连接成功后,执行回射客户端的函数
echo_cli(sock);
return 0;
}
三:实现数据报模式客户/服务器回射程序
//服务端
#include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define MAXLINE 4096
#define UNIX_DGRAM_PATH "/tmp/unix_dgram_path"
void dg_echo(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
while(1) {
len = clilen;
n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
sendto(sockfd, mesg, n, 0, pcliaddr, len);
}
}
int main(int argc, char **argv)
{
int ret, sockfd;
struct sockaddr_un cliaddr, servaddr;
sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("socket error: %s\n", strerror(errno));
return -1;
}
unlink(UNIX_DGRAM_PATH);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strncpy(servaddr.sun_path, UNIX_DGRAM_PATH, sizeof(servaddr.sun_path) - 1);
ret = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (ret) {
printf("bind error: %s\n", strerror(errno));
return -1;
}
dg_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
}
//客户端
#include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#define MAXLINE 4096
#define UNIX_DGRAM_PATH "/tmp/unix_dgram_path"
void dg_cli(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
while (fgets(sendline, MAXLINE, fp) != NULL) {
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0;
fputs(recvline, stdout);
}
}
int main(int argc, char **argv)
{
int ret, sockfd;
struct sockaddr_un cliaddr, servaddr;
sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("socket error: %s\n", strerror(errno));
return -1;
}
bzero(&cliaddr, sizeof(cliaddr));
cliaddr.sun_family = AF_LOCAL;
strncpy(cliaddr.sun_path, tmpnam(NULL), sizeof(cliaddr.sun_path) - 1);
ret = bind(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
if (ret) {
printf("bind error: %s\n", strerror(errno));
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strncpy(servaddr.sun_path, UNIX_DGRAM_PATH, sizeof(servaddr.sun_path) - 1);
dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
return 0;
}
四:利用socketpair实现全双工通信
#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);/* 成功返回非0,失败返回-1 */
@socketpair函数:该函数与unix的pipe函数类似,返回两个彼此连接的描述符
@int :成功返回非0,失败返回-1
@family:AF_LOCAL
@type:SOCK_STREAM或者SOCK_DGRAM
@protocol:默认为0
@sockfd[2]:新创建的两个套接字描述符作为sockfd[0],sockfd[1]返回
//在父子进程间实现全双工的通信
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
//父子进程间实现全双工通信
int main(void)
{
//定义一个数组用来接收套接字对
int sockfds[2];
//全双工通信的流管道,sockfds[0]和sockfds[1]都是即可读又可写
if(socketpair(PF_UNIX,SOCK_STREAM,0,sockfds)<0)
ERR_EXIT("socketpair");
pid_t pid;
//在父子进程间实现全双工的通信
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
//父进程
if(pid>0)
{
int val=0;
close(sockfds[1]);
while(1)
{
++val;
printf("sending data: %d\n",val);
//父进程写给子进程
write(sockfds[0],&val,sizeof(val));
//sockfds既可读又可写,用于接收回来
read(sockfds[0],&val,sizeof(val));
printf("data received: %d\n",val);
}
}
//子进程
else if(pid==0)
{
int val;
close(sockfds[0]);
while(1)
{
//sockfds[1]既能读又能写
read(sockfds[1],&val,sizeof(val));
++val;
write(sockfds[1],&val,sizeof(val));
}
}
return 0;
}