• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

linux网络通信

武飞扬头像
东北马里奥
帮助1

linux网络编程

 


一、socket

1.常见函数详解

1.创建socket:
	int socket(int domain, int type, int protocol); 
		domain: AF_INET 这是大多数用来产生 socket 的协议,使用 TCP 或 UDP 来传输,用 IPv4 的地址
			 	AF_INET6 与上面类似,不过是来用 IPv6 的地址
	   		    AF_UNIX 本地协议
		type:SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的 socket 类型,这个socket 是使用 TCP 来进行传输。 
				SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用 UDP 来进行它的连接
		protocol: 传 0 表示使用默认协议。	protcol是0表示,根据type的值选择合适的协议。		
		                当type是sock_stream时。protoc是tcp
						当type是sock_dgram时.protcol是udp
		成功:返回指向新创建的 socket 的文件描述符,失败:返回-1,设置 errno	
		
	2.绑定socket的端口和ip	
		Int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
			sockfd:套接字的fd(文件描述符),socket()函数的返回值
			addr:结构体 ip port(端口)
				struct sockaddr_in{ (涉及强制转换sockaddr_in ->sockaddr  参考)
					short int sin_family;              //地址族
					unsigned short int sin_port;       //端口号
					struct in_addr sin_addr;           //IP地址
				}
				struct in_addr {
				     __be32 s_addr;
				};
	3.设置sockefd的同时最大的链接数			
	int listen(int sockfd,int backlog);
		backlog:请求链接客户端队列的最大存放数目
		返回值:判断成功失败
		
	4.等待某个sockfd的客户端响应
		int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen)
			addr:传出参数,跟我建立连接的客户端的结构体(内含客户端ip 端口)
			返回值:连接客户端的套接字。
			当有一个client响应listenfd时,accept会返回一个connfd与客户端通信。
	5.client与server通信
		int connect(int sockfd,struct sockaddr*serv_addr,int addrlen)
			sockfd:传入参数,文件描述符绑定连接成功的服务器套接字便于在客户端对服务器进行IO操作
			serv_addr:绑定我要链接服务器的结构体(需要初始化绑上ip和断口),表明目的



学新通

学新通

2.服务器代码

思路:利用socket创建一个listenfd,bind其服务器ip和端口,listen设置其最大通信数,accept阻塞监听listen,当有client响应时,accept返回一个connfd与client通信。listenfd始终作为accept的换入参数,监听client。每当有client与其通行就设置新的connfd语气通信。相当于connfd是绑定client端ip和端口的fd,server只需要读写connfd就可以与其通信。

单进程服务器
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define SER_PORT 8000
int main(void)
{
	int sockfd,connfd;//
	int len;
	char wbuf[1024];
	char rbuf[1024];
	struct sockaddr_in serveraddr,clientaddr; //两个结构体 一个用于绑定身份到套接字  一个用于接收客服端的结构体
	//1.创建监听套接字
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	//2.bind(通信需要套接字 把家的地址 门牌号绑上去 ip和端口)
	bzero(&serveraddr,sizeof(serveraddr)); //类似memset 清空结构体
	//地址族协议,选择IPV4
	serveraddr.sin_family = AF_INET;     //属于ipv4还是ipv6
	//IP地址 本机任意可用ip地址
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(SER_PORT);//端口号	
	bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	//3.监听 和服务器连接的总和
	listen(sockfd,128);	
	int size = sizeof(clientaddr);
	//4.accept 阻塞监听 客服端链接的请求
	connfd = accept(sockfd,(struct sockaddr *)&clientaddr,&size); 	
	//输出客服端的ip和端口
	char ipstr[128];
	printf("client ip%s ,port %d\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)),
		ntohs(clientaddr.sin_port));
	//5.处理客户端请求
	//读和写
	while(1)
	{
		memset(wbuf,0,sizeof(wbuf));//清空
		memset(rbuf,0,sizeof(wbuf));	
		//接收消息	
		int len = read(connfd,rbuf,sizeof(rbuf));
		if(len==0)//表示断开连接
		{
			printf("client is close....\n");
		}
		printf("receive from client:%s",rbuf);
		//发送消息
		printf("send to client:");
		fgets(wbuf,sizeof(wbuf),stdin);
		write(connfd,wbuf,strlen(wbuf));	
	}
	close(connfd);
	close(sockfd);
	return 0;
}

多进程服务器
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

void do_sigchild(int num)
{
    while (waitpid(0, NULL, WNOHANG) > 0)
        ;
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int i, n;
    pid_t pid;
    struct sigaction newact;

    newact.sa_handler = do_sigchild;
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    sigaction(SIGCHLD, &newact, NULL);

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    Listen(listenfd, 20);

    printf("Accepting connections ...\n");
    while (1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        printf("-------------------------%d\n", connfd);

        pid = fork();
        if (pid == 0) {
            Close(listenfd);
            while (1) {
                n = Read(connfd, buf, MAXLINE);
                if (n == 0) {
                    printf("the other side has been closed.\n");
                    break;
                }
                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(buf[i]);

                Write(STDOUT_FILENO, buf, n);
                Write(connfd, buf, n);
            }
            Close(connfd);
            return 0;
        } else if (pid > 0) {
            Close(connfd);
        }  else
            perr_exit("fork");
    }
    return 0;
}



学新通

3.客户端代码

代码思路:创建connfd并调用connect与server的ip和端口通信,值得注意的是,connfd是可以不使用bind与client的ip和端口绑定,在调用connect的时候,会自动绑定。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define SER_PORT 8000
int main(void)
{
	int sockfd;
	struct sockaddr_in serveraddr;
	int len;
	char wbuf[1024],rbuf[1024];
	//1、socket 通信用套接字,创建一个sockfd
	sockfd = socket(AF_INET,SOCK_STREAM,0);
    char ipstr[]="127.0.0.1";
	//2、编辑要连接的服务器地址,并绑定
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;            //设置地址族协议
	serveraddr.sin_port = htons(SER_PORT);      //设置端口号
	inet_pton(AF_INET,ipstr,&serveraddr.sin_addr.s_addr);//设置ip地址   点分十进制转成网络字节序
	//2、connect 连接服务器 sockfd传出服务器套接字
	connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
	//3、读写
	while(1)
	{
		memset(wbuf,0,sizeof(wbuf));
		memset(rbuf,0,sizeof(rbuf));
		//发送消息
		printf("send to server:");
		fgets(wbuf,sizeof(wbuf),stdin);
		write(sockfd,wbuf,strlen(wbuf));
		
		//接收消息
		len=read(sockfd,rbuf,sizeof(rbuf));
		if(len==0)//表示断开连接
		{
			printf("server is close....\n");
		}
		printf("receive from server:%s",rbuf);
		
		
	}
	//4、close
	close(sockfd);
	return 0;
}

学新通

二、select

1.IO复用的概念

在socket模型中,我们阻塞监听listenfd,使进程阻塞在accept中。IO复用是指,非阻塞循环监听server中的connfd和listenfd,当有fd准备好,进程运行到非阻塞循环监听函数时,会运行响应的程序。
多路复用IO就是用一条线程,同时监听多个IO请求,并且在有IO请求产生的时候返回。注意,虽然我们的IO多路复用也会阻塞,但是这里的阻塞是应用层面的,也就是说在多路复用的方法上进行阻塞,而不是在操作系统层面去阻塞。

2.常见函数详解

1.int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 被监听的文件描述符总数,它会比文件描述符表集合中的文件描述符表的最大值大1,因为文件描述符从0开始计数
readfds:需要监听的可读事件的文件描述符集合(如果有动静则该集合只会保留产生可读事件)看下面实例
writefds:需要监听的可写事件的文件描述符集合
exceptfds:需要监听的异常事件的文件描述符集合
timeout:即告诉内核select等待多长时间之后就放弃等待。一般设为NULL 表示无动静就阻塞等待

2.set相关函数,set是一个1024的数组,可以记录每个fd。
FD_ZERO(fd_set* set)
理解为初始化文件描述符几何 (既把所有位都设置为0)
FD_SET(fd,fd_set* set)
理解为把文件描述符加入集合(本质为对应fd位设置为1)
FD_CLR(fd,fd_set* set)
理解为把文件描述符从集合取出(本质为对应fd位设置为0)
FD_ISSET(fd,fd_set* set)
理解为检测改文件描述符是否在集合(本质为对应的fd位是否设置为1)

学新通

3.服务器代码

代码思路:创建listenfd并将其放到fd集合set中,调用select阻塞监听集合set,当select返回时,判断set中是否还存在listenfd,如果有则说明有新的client与server通信,调用accept返回client的ip和端口并得到一个新的connfd,将其放到set中。继续阻塞监听select的set,若返回的set有listenfd则调用accept,将新的client的connfd加入到集合中,若是返回的set有connfd则与对应的client通信。

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <signal.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#define SET_PORT 8000
int main(int argc, char *argv[])
{
    int sockfd,connfd;//监听套接字 和连接套接字
    struct sockaddr_in serveraddr;
    int i;//主要用于各种for循环的i
    int on = 1;//只有下方设置可重复性使用的端口用到
     //1.创建监听套接字
    sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) ;//设置为可重复使用的端口
    
    //2.bind(通信需要套接字 我把我家的地址 门牌号绑上去 ip和端口)
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SET_PORT);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr));
    //3.监听 和服务器连接的总和
    listen(sockfd,128) ;  
    int maxfd = sockfd;
    //初始化两个集合 一个数组
    int client[FD_SETSIZE];//数组用于存放客服端fd 循环查询使用 FD_SETSIZE 是一个宏 默认已经最大1024
    fd_set rset;//放在select中 集合中都是有动静的fd 一般就一个
    fd_set allset;//只做添加fd
    FD_ZERO(&rset);//清空的动作
    FD_ZERO(&allset);//清空的动作
    
    //先将监听套接字放入
    FD_SET(sockfd,&allset);
    int nready;
    
    //初始化数组都为-1 应为标识符从0开始
    for(i = 0; i < FD_SETSIZE; i  )
        client[i] = -1;
    while(1)
    {
    	//非常关键的一步
        rset = allset;//保证每次循环 select能监听所有的文件描述符 因为rset只会剩下有动静的
        nready = select(maxfd 1,&rset,NULL,NULL,NULL);//参数一
        
        //新客户端
        if(FD_ISSET(sockfd,&rset))
        {
            struct sockaddr_in clientaddr;  
            memset(&clientaddr,0,sizeof(clientaddr));
            int len = sizeof(clientaddr);
            
            connfd = accept(sockfd, (struct sockaddr*)&clientaddr,&len);
            
            char ipstr[128];//打印用到
            printf("client ip%s ,port %d\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)),
                   ntohs(clientaddr.sin_port));
            
            //做的事情一:文件df放入数组
            for(i = 0; i < FD_SETSIZE; i  )
            {	if(client[i] < 0)
                {
                    client[i] = connfd;
                    break;//一定要记得及时跳出 易错点
                }
            }
            //做的事情二:放入集合
            FD_SET(connfd,&allset);//放入集合
            //防止超出范围//select的第一个参数必须是监视的文件描述符 1 如果不断有新的客户连接 最大值不断变大 超出就赋值
            if(connfd > maxfd)
                maxfd = connfd;
            
            //下方表示 如果同一时刻只有 一个动静 就无需进入下方的else判断处理 如果不止一个 nready-1 再进入下方判断
            if(--nready <= 0)  
            	continue;
            
        }
        else
        {
            //已连接FD产生可读事件
            for(i = 0; i < FD_SETSIZE; i  )//FD_SEISIZE 是宏 1024  //循环从数组取出元素比对
            {		
                
                if(FD_ISSET(client[i],&rset))
                {
                    connfd = client[i];
                    char buf[1024] = {0};
                    int nread ;
                    nread = read(connfd, buf, sizeof(buf));
                    if(nread == 0)//表示客服端断开链接
                    {
                        //四步处理 打印说明 从集合中删除  从数组中删除 关闭客服端
                        printf("client is close..\n");
                        FD_CLR(connfd, &allset);
                        client[i] = -1;
                        close(connfd);
                    }
                    else//正常读到处理
                    {
                        write(connfd,buf,nread);
                        memset(buf,0,1024); 
                        
                    }
                   //下方表示如果同意时刻如果可读事件只有一个 无需再将数组元素进行循环比对 直接跳出
                    //不必让循环走完 浪费时间
                    if(--nready <= 0)
                        break;
                }
            }
        }
    }    
    return 0;
}


学新通

三.epool

1.epool和select的区别

epoll不存在集合的覆盖 epoll_create会返回一个fd,指向空间包含全部的事件(结构体)
epoll把要监听的每一个fd都包装成一个事件,并把这个事件记入epollfd 让epollfd来监听
select产生动静是吧fd放入集合 但是epoll通过epoll_wait 把产生动静的fd所包装好的事件放入结构体数组
select需要备份,需要重新创建数组放fd循环比对,epoll直接通过包装好的事件(结构体)就能获得fd,效率也更快(差别主要体现在这)
两者的区别是的select适合用户客服端不多的情况,而epoll没有客户端的上限

2.常用的函数

#include <sys/epoll.h>

int epoll_create(int size);
作用:创建一个epoll句柄,告诉他需要监听的数目(也可以理解成申请一片空间,用于存放监听的套接字)

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
作用:控制某个epoll监控的文件描述符上的事件:注册,修改、删除(也就是增添 删除 修改一个事件)

int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
作用:监听红黑树上的事件,将产生动静的事件放在event这个数组内,


int epoll_create(int size);
参数一:通知内核监听size个fd,只是个建议值并与硬件有关系。(从 Linux 内核 2.6.8 版本起,size 这个参数就被忽略了,只要求 size 大于 0 即可)
返回值:返回epoll句柄(fd)

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数一:int epfd:epoll_create()的返回值
参数二:int op: 表示动作,用三个宏来表示
EPOLL_CTL_ADD(注册新的fd到epfd)
EPOLL_CTL_MOD(修改已经注册的fd监听事件)
EPOLL_CTL_DEL(从epfd删除一个fd)
参数三:int fd 操作对象(socket)
参数四:struct epoll_evevt* evevt; 告诉内核需要监听的事件

int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
参数一:int epfd:epoll_create()函数返回值
参数二:struct epoll_events* events用于回传代处理事件的数组(也就是存放产生动静的事件)
参数三:int maxevents 同时最多产生多少事件,告诉内核events有多大,该值必须大于0
参数四:int timeout表示 -1相当于阻塞,0相当于非阻塞,超时时间(单位:毫秒)
返回值:成功返回产生动静事件的个数


	struct epoll_event {
	__uint32_t events; 宏定义读和写EPOLLIN读EPOLLOUT,边缘触发EPOLLET					
	epoll_data_t data; 联合体
	};
	
	联合体如下:
	typedef union epoll_data {
	void *ptr;//此指针可以指向一个带有函数的结构体,当有对应的事件发生时,可以将次指针指向的结构体内的函数手动进行调用。这就是反应堆模型。
	int fd;  //一般通过此fd判断是否是对应的listenfd或connfd进行判断是否有新的client链接和哪个旧的client在发送数据
	__uint32_t u32;
	__uint64_t u64;
	} epoll_data_t;

Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发只要有数据都会触发


学新通

3.服务区代码

代码思路:epool_create创建红黑树,返回epollfd并指向红黑树。设置listenfd的event结构体,调用epool_ctl将listenfd上树,epool_wait监听上树的fd,epool_wait有响应时,通过epool_wait的传出参数struct epoll_event * events检查fd,如果events中listenfd则表示有新的client与server通信,这时调用accept得到connfd和client的ip和端口,并将新的connfd上树,再监听红黑树,当有旧的client通信则利用connfd与其通信。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define MAXLINE 80
#define SERV_PORT 8000 //端口号
#define OPEN_MAX 1024  //最多连接数



int main(int argc,char *argv[])
{	
	int maxi,sockfd,connfd;//sockfd 用来监听 connfd用来连接的· 监听套接字要自己创建  连接套接字等待返回就行不需要socket
	int nready,efd; //efd是 epoll模型标识符 nready 产生的动静数
	struct sockaddr_in clientaddr, serveraddr;
	struct epoll_event event, events[OPEN_MAX]; //一个果篮 一个结构体
	
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serveraddr, sizeof(serveraddr));
	bzero(&clientaddr, sizeof(clientaddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(SERV_PORT);
	int on =1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) ;//设置为可重复使用的端口
    bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));
    listen(sockfd, OPEN_MAX);//服务器套接字
	efd=epoll_create(OPEN_MAX);
	/*包装服务器fd为事件*/
	event.events=EPOLLIN|EPOLLET;
	event.data.fd=sockfd;
    epoll_ctl(efd,EPOLL_CTL_ADD,sockfd,&event);//把服务器fd包装成事件放在红黑树上	
    while(1)
	{
		//参数一 epollfd 参数二 产生动静的结构体数组 参数三 同时最多产生多少动静 参数四 超市时间
		nready=epoll_wait(efd,events,OPEN_MAX,-1);//返回值为动静数量
		for(int i=0;i<nready;i  )
		{        	
			if(!(events[i].events & EPOLLIN))//判断为可读事件 不是立刻返回循环 与select不同
				continue;
			if (events[i].data.fd==sockfd)//表示有新的连接
			{
				int len=sizeof(clientaddr);
				char ipstr[128];//打印用到
				connfd=accept(sockfd,(struct sockaddr *)&clientaddr,&len);
				printf("client ip%s ,port %d\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)),
					ntohs(clientaddr.sin_port));
				event.events = EPOLLIN|EPOLLET; 
				event.data.fd = connfd;  
				epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);			
			}
			else//表示旧的数据产生可读事件(1 客户端发来数据 2 客户端断开链接)
			{				
     			connfd=events[i].data.fd;
				char buf [1024];
				memset(buf,0,1024);
				int nread;		
				nread=read(connfd,buf,sizeof(buf));
				
				if(nread==0)
				{
					printf("client is close..\n"); //打印
					epoll_ctl(efd, EPOLL_CTL_DEL, connfd, NULL);//删除果子 select是从集合 和 数组 删除
					close(connfd);//关闭客服端 select一样
                }
				else
				{
					printf("%s",buf);
				}			
			}		
		}	
	}	
}

学新通

四、udp通信

1.实现方法及其代码

创建socket时选择SOCK_DGRAM,则选择了用udp的协议,udp一次读写,没有稳定的链接,一般直接用recvfrom和sendto进行读写。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
	 sockfd: 套接字 
	 buf:缓冲区地址 
	 len:缓冲区大小 
	 flags: 0
	 src_addr:(struct sockaddr *)&addr 传出。 对端地址结构
	 addrlen:传入传出。 
	 返回值: 成功接收数据字节数。 失败:-1 errn。 0: 对端关闭。
   
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); 
    sockfd: 套接字 
    buf:存储数据的缓冲区 
    len:数据长度 
    flags: 0 
    src_addr:(struct sockaddr *)&addr 传入。目标地址结构 
    addrlen:地址结构长度。 
    返回值:成功写出数据字节数。 失败 -1, errno

学新通
注意recefrom和sendto的使用方法
server端
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>

#define SERV_PORT 8000

int main(void)
{
    struct sockaddr_in serv_addr, clie_addr;
    socklen_t clie_addr_len;
    int sockfd;
    char buf[BUFSIZ];
    char str[INET_ADDRSTRLEN];
    int i, n;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(SERV_PORT);

    bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    printf("Accepting connections ...\n");
    while (1) {
        clie_addr_len = sizeof(clie_addr);
        n = recvfrom(sockfd, buf, BUFSIZ,0, (struct sockaddr *)&clie_addr, &clie_addr_len);
        if (n == -1)
            perror("recvfrom error");

        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
                ntohs(clie_addr.sin_port));

        for (i = 0; i < n; i  )
            buf[i] = toupper(buf[i]);

        n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
        if (n == -1)
            perror("sendto error");
    }

    close(sockfd);

    return 0;
}

client端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>

#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    int sockfd, n;
    char buf[BUFSIZ];

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    while (fgets(buf, BUFSIZ, stdin) != NULL) {
        n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
        if (n == -1)
            perror("sendto error");

        n = recvfrom(sockfd, buf, BUFSIZ, 0, NULL, 0);         //NULL:不关心对端信息
        if (n == -1)
            perror("recvfrom error");

        write(STDOUT_FILENO, buf, n);
    }

    close(sockfd);

    return 0;
}

学新通

五、本地套接字

1.实现要点

学新通

Socket(AF_UNIX, SOCK_STREAM, 0)选择AF_UNIX,则选择了本地通信。

在网络通信中,使用sockaddr_in去设置ip和端口,并在bind和accept等函数中将其临时强转成sockaddr,这是历史原因造成的。但在本地通信中,设置的是sockaddr_un结构体,其108字节的空间并未全部使用,所以得用offsetof进行计算其长度,而不能想sockaddr_in中使用sizeof(判断某种类型的空间大小)计算。
len = offsetof(struct sockaddr_un, sun_path) strlen(servaddr.sun_path);

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 文件名(含路径)
   };
客户端和服务器端创建socket,将socket绑定各自的文件然后进行通信。服务器端要bind其sockadrr_un,客户端也要bind其sockadrr,不能自动绑定。
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>
#include "wrap.h"

#define SERV_ADDR  "serv.socket"

int main(void)
{
    int lfd, cfd, len, size, i;
    struct sockaddr_un servaddr, cliaddr;
    char buf[4096];
    lfd = Socket(AF_UNIX, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path, SERV_ADDR);
    len = offsetof(struct sockaddr_un, sun_path)   strlen(servaddr.sun_path);     /* servaddr total len */
    unlink(SERV_ADDR);                              /* 确保bind之前serv.sock文件不存在,bind会创建该文件 */
    Bind(lfd, (struct sockaddr *)&servaddr, len);           /* 参3不能是sizeof(servaddr) */

    Listen(lfd, 20);
    printf("Accept ...\n");
    while (1) {
        len = sizeof(cliaddr);  //AF_UNIX大小 108B

        cfd = Accept(lfd, (struct sockaddr *)&cliaddr, (socklen_t *)&len);

        len -= offsetof(struct sockaddr_un, sun_path);      /* 得到文件名的长度 */
        cliaddr.sun_path[len] = '\0';                       /* 确保打印时,没有乱码出现 */

        printf("client bind filename %s\n", cliaddr.sun_path);

        while ((size = read(cfd, buf, sizeof(buf))) > 0) {
            for (i = 0; i < size; i  )
                buf[i] = toupper(buf[i]);
            write(cfd, buf, size);
        }
        close(cfd);
    }
    close(lfd);

    return 0;
}


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>

#include "wrap.h"

#define SERV_ADDR "serv.socket"
#define CLIE_ADDR "clie.socket"

int main(void)
{
    int  cfd, len;
    struct sockaddr_un servaddr, cliaddr;
    char buf[4096];

    cfd = Socket(AF_UNIX, SOCK_STREAM, 0);

    bzero(&cliaddr, sizeof(cliaddr));
    cliaddr.sun_family = AF_UNIX;
    strcpy(cliaddr.sun_path,CLIE_ADDR);

    len = offsetof(struct sockaddr_un, sun_path)   strlen(cliaddr.sun_path);     /* 计算客户端地址结构有效长度 */

    unlink(CLIE_ADDR);
    Bind(cfd, (struct sockaddr *)&cliaddr, len);                                 /* 客户端也需要bind, 不能依赖自动绑定*/

    
    bzero(&servaddr, sizeof(servaddr));                                          /* 构造server 地址 */
    servaddr.sun_family = AF_UNIX;
    strcpy(servaddr.sun_path, SERV_ADDR);

    len = offsetof(struct sockaddr_un, sun_path)   strlen(servaddr.sun_path);   /* 计算服务器端地址结构有效长度 */

    Connect(cfd, (struct sockaddr *)&servaddr, len);

    while (fgets(buf, sizeof(buf), stdin) != NULL) {
        write(cfd, buf, strlen(buf));
        len = read(cfd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
    }

    close(cfd);

    return 0;
}
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgakbkj
系列文章
更多 icon
同类精品
更多 icon
继续加载