ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SOCKET应用实例

2020-12-08 19:29:07  阅读:198  来源: 互联网

标签:sockaddr SOCKET ai int hints 实例 应用 include struct


SOCKET应用实例

这里给出两个编程实例代码,一个是面向流式套接字c/s例子,一个是非阻塞的多人聊天服务器示例。

1.面向流式套接字c/s例子

准备

打开Ubuntu、远程连接树莓派,分别在树莓派和Ubuntu上编写服务端和客户端代码并编译。
ubuntu:

mkdir daima  //创建文件夹
cd daima  //进入文件夹
gedit server.c  //创建 server.c 文件
gcc server.c -o server

树莓派

mkdir daima  //创建文件夹
cd daima  //进入文件夹
nano client.c  //创建 client.c 文件
gcc client.c -o client

先运行服务端(server)后运行客户端(client)。

代码

client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT "9090"  //the port client will be connecting to
#define MAXDATASIZE 100  //max number of bytes we can get at once

//get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
	if(sa->sa_family == AF_INET)
	{
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
	int sockfd, numbytes;
	char buf[MAXDATASIZE];
	struct addrinfo hints, *servinfo, *p;
	/*
	typedef struct addrinfo { 
    		int ai_flags;        //AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST 
    		int ai_family;        //AF_INET,AF_INET6 
    		int ai_socktype;    //SOCK_STREAM,SOCK_DGRAM 
    		int ai_protocol;    //IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc. 
    		size_t ai_addrlen;            //must be zero or a null pointer 
    		char* ai_canonname;            //must be zero or a null pointer 
    		struct sockaddr* ai_addr;    //must be zero or a null pointer 
    		struct addrinfo* ai_next;    //must be zero or a null pointer 
	}
	其中ai_flags、ai_family、ai_socktype说明如下: 
	参数		取值			值	说明 
	ai_family	AF_INET			2	IPv4 
				AF_INET6		23	IPv6 
				AF_UNSPEC		0	协议无关 
	ai_protocol	IPPROTO_IP		0	IP协议 
				IPPROTO_IPV4    4   IPv4 
		        IPPROTO_IPV6    41  IPv6 
		        IPPROTO_UDP     17  UDP 
		        IPPROTO_TCP     6   TCP 
	ai_socktype SOCK_STREAM     1   流 
                SOCK_DGRAM      2   数据报 
	ai_flags    AI_PASSIVE      1   被动的,用于bind,通常用于server socket 
                AI_CANONNAME    2 
                AI_NUMERICHOST  4   地址为数字串 
	*/
	int rv;
	char s[INET6_ADDRSTRLEN];
	//如果命令行参数不等于 2 ,则执行下面的语句
	if(argc != 2)
	{
		fprintf(stderr, "usage:client hostname\n");  //打印错误消息
		exit(1);  //退出
	}
	//将hints内存的内容置 0
	memset(&hints, 0, sizeof hints);
	//设置协议无关
	hints.ai_family = AF_UNSPEC;
	//设置套接为流
	hints.ai_socktype = SOCK_STREAM;
	/*
	int getaddrinfo( const char *hostname, const char *service, 
		const struct addrinfo *hints, struct addrinfo **result );
	参数说明
	hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
	service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
	hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,
		调用者在这个结构中填入关于期望返回的信息类型的暗示。
		举例来说:指定的服务既可支持TCP也可支持UDP,
		所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM,
		使得返回的仅仅是适用于数据报套接口的信息。
	result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
	返回值:0——成功,非0——出错

	getaddrinfo 函数能够处理名字到地址以及服务到端口这两种转换,
		返回的是一个sockaddr结构的链表而不是一个地址清单。
		这些sockaddr结构随后可由套接口函数直接使用。
		如此一来,getaddrinfo函数把协议相关性安全隐藏在这个库函数内部。
		应用程序只要处理由getaddrinfo函数填写的套接口地址结构。
	*/
	if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
	{
		fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv));
		return 1;
	}
	//遍历所有返回结果并链接到第一个成功连接的套接
	for(p = servinfo; p != NULL; p = p->ai_next)
	{
		//创建一个套接字
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
		{
			perror("client:socket");
			continue;
		}
		//连接状态判断
		if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
		{
			close(sockfd);
			perror("client:connect");
			continue;
		}
		//如果创建套接字成功且连接成功,则退出循环
		break;
	}
	//如果套接口地址为空,则打印结果
	if(p == NULL)
	{
		fprintf(stderr, "client:failed to connect\n");
		return 2;
	}
	//inet_ntop 函数可以将 IP 地址在“点分十进制”和“整数”之间转换
	inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s);
	printf("client:connecting to %s\n",s);
	//freeaddrinfo 函数释放 getaddriinfo 函数返回的存储空间
	freeaddrinfo(servinfo);
	//recv 函数用于判断缓冲区数据传输的状态,传输异常则打印消息比并退出
	if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
	{
		perror("recv");
		exit(1);
	}
	//将字符数组的最后一位置 \0 ,用于后面一次性输出
	buf[numbytes] = '\0';
	printf("client:received %s\n",buf);
	close(sockfd);
	return 0;
}

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define PORT "9090"  //the port users will be connecting to
#define BACKLOG 10  //how many pending connections queue will hold
void sigchld_handler(int s)
{
	//Waitpid temporarily stops the execution of the current process until a signal arrives or the child process terminates.
	while(waitpid(-1, NULL, WNOHANG) > 0);
}
//get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
	if(sa->sa_family == AF_INET)
	{
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
	int sockfd, new_fd;  //listen on sock_fd,new connection on new_fd
	struct addrinfo hints, *servinfo, *p;
	struct sockaddr_storage their_addr;  //connector's address information
	socklen_t sin_size;
	//The data type "socklen_t" and int should have the same length. 
	//Otherwise, you break the padding of the BSD socket layer.
	struct sigaction sa;
	//Sigaction is a function that can be used to query or set up signal processing
	int yes = 1;
	char s[INET6_ADDRSTRLEN];
	int rv;
	//Set the Hints memory to zero
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;  //use my IP
	if((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0)
	{
		fprintf(stderr, "getaddrinfo:%s\n", gai_strerror(rv));
		return 1;
	}
	//loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next)
	{
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
		{
			perror("server:socket");
			continue;
		}
		if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
		{
			perror("setsockopt");
			exit(1);
		}
		if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
		{
			close(sockfd);
			perror("server:bind");
			continue;
		}
		break;
	}
	//If the pointer P is null, an error message is printed
	if(p == NULL)
	{
		fprintf(stderr, "server:failed to bind\n");
		return 2;
	}
	//all done with this structure
	freeaddrinfo(servinfo);
	/*
	Leave a socket in the state of listening for incoming connection requests
	If the listening fails, exit
	*/
	if(listen(sockfd, BACKLOG) == -1)
	{
		perror("listen");
		exit(1);
	}
	sa.sa_handler = sigchld_handler;  //reap all dead processes
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if(sigaction(SIGCHLD, &sa, NULL) == -1)
	{
		perror("sigaction");
		exit(1);
	}
	printf("server:waiting for connections...\n");
	//main accept() loop
	while(1) 
	{
		sin_size = sizeof their_addr;
		new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);
		if(new_fd == -1)
		{
			perror("accept");
			continue;
		}
		inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr*)&their_addr), s, sizeof s);
		printf("server:got connection from %s\n",s);
		if(!fork())  //this is the child process
		{
			close(sockfd);  //child doesn't need the listener
			if(send(new_fd, "Hello,world!", 13, 0) == -1)
				perror("send");
			close(new_fd);
			exit(0);
		}
		close(new_fd);  //parent doesn't need this
	}
	return 0;
}

运行结果:
在这里插入图片描述

2.非阻塞的多人聊天服务器端

至少准备三个(Ubuntu 或者 树莓派)Linux 系统:一个充当服务器端,另外两个充当客户端(必须连上同一个手机热点)

代码

server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORT "9090"  //port we're listening on
//get sockaddr,IPv4 or IPv6:
//sockaddr 结构体:存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址
void *get_in_addr(struct sockaddr *sa)
{
    if(sa->sa_family == AF_INET){
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    fd_set master;  //主文件描述符列表
    fd_set read_fds;  //select() 的临时文件描述符列表
    /*
    当调用 select() 时,由内核根据 IO 状态修改 fd_set 的内容
    由此来通知执行了 select() 的进程哪一 socket 或文件发生了可读或可写事件
    */
    int fdmax;  //最大文件描述符
    int listener;  //监听套接字描述符
    int newfd;  //新接受的套接字描述符
    //sockaddr_storage 结构体:存储套接字地址信息
    struct sockaddr_storage remoteaddr;  //客户端地址
    socklen_t addrlen;//socklen_t 和 int 相同长度的一种类型

    char buf[256];  //用于客户端数据的缓冲区
    int nbytes;

    int yes=1;  //for setsockopt() SO_REUSEADDR,below
    int i,j,rv;
    char remoteIP[INET_ADDRSTRLEN];

    struct addrinfo hints,*ai,*p;  //地址信息结构体

    FD_ZERO(&master);  //清除主文件描述符列表
    FD_ZERO(&read_fds);  //清楚临时文件描述符列表

    //给我们一个套接字并绑定它
    memset(&hints, 0, sizeof hints);  //地址信息置零
    hints.ai_family = AF_UNSPEC;  //AF_UNSPEC(协议无关)
    hints.ai_socktype = SOCK_STREAM;  //SOCK_STREAM(流)
    hints.ai_flags = AI_PASSIVE;  //AI_PASSIVE(被动的,用于 bind)
    if((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
    {
        fprintf(stderr, "selectserver:%s\n", gai_strerror(rv));
        exit(1);
    }

    //遍历所有的结果
    for(p = ai; p != NULL; p = p->ai_next)
    {
        //创建套接字并赋值给 listener 套接字描述符
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if(listener < 0)
        {
            continue;
        }
        //setsockope 函数用于任意类型、任意状态套接字的设置选项
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
        /*
        bind 函数:用于未连接的数据报或流类套接口,把一本地址与一套接口捆绑
        在 connect() 或 listen() 调用前使用
        */
        if(bind(listener, p->ai_addr, p->ai_addrlen) < 0)
        {
            close(listener);
            continue;
        }
        break;
    }

    if(p == NULL)
    {
        fprintf(stderr, "selectserver:failed to bind\n");
        exit(2);
    }

    //到了这里,意味着服务器的地址和端口绑定完成,可以释放 ai 的内存
    freeaddrinfo(ai);  

    /*
    listen 函数:创建一个套接口并监听申请的连接.
    参数一:用于标识一个已捆绑未连接套接口的描述符
    参数二:等待连接队列的最大长度(这里是 10)
    */
    if(listen(listener, 10) == -1)
    {
        perror("listen");
        exit(3);
    }

    //将侦听器添加到主文件
    FD_SET(listener, &master);

    //跟踪最大的文件描述符
    fdmax = listener;  //到目前为止,是这个

    //主循环
    for(;;)
    {
        read_fds = master;  //将主文件描述符表复制到临时文件描述符表

        /*
        select 函数:确定一个或多个套接口的状态,如需要则等待。
        原型:int select( int nfds, fd_set FAR* readfds, fd_set * writefds, 
                         fd_set * exceptfds, const struct timeval * timeout);
        nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,
              不能错!在Windows中这个参数的值无所谓,可以设置不正确。
        readfds:(可选)指针,指向一组等待可读性检查的套接口。
        writefds:(可选)指针,指向一组等待可写性检查的套接口。
        exceptfds:(可选)指针,指向一组等待错误检查的套接口。
        timeout:select()最多等待时间,对阻塞操作则为 NULL。
        */
        if(select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1)
        {
            perror("select");
            exit(4);
        }

        //运行现有的连接,查找要读取的数据
        for(i = 0; i <= fdmax; i++)
        {
            /*
            宏原型:int FD_ISSET(int fd,fd_set *fdset)
            在调用 selelct() 函数后,用 FD_ISSET 来检测 fd 在 fdset 集合中的状态是否变化
            返回整形,当检测到 fd 状态发生变化时返回真,否则返回假
            */
            if(FD_ISSET(i, &read_fds))  //得到了一个连接
            {
                if(i == listener)//如果新连接为最大文件描述符
                {
                    //处理新连接
                    addrlen = sizeof remoteaddr;
                    newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);

                    if(newfd == -1)
                    {
                        perror("accept");
                    }
                    else
                    {
                        FD_SET(newfd, &master);  //添加到主文件描述符列表
                        if(newfd > fdmax)  //记录最大值
                        {
                            fdmax = newfd;
                        }
                        printf("selectserver:new connection from %s on socket %d\n", inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET_ADDRSTRLEN), newfd);
                    }
                }
                else
                {
                    //处理来自客户端的数据
                    if((nbytes = recv(i, buf, sizeof buf, 0)) <= 0)
                    {
                        //出现错误或连接被客户端关闭
                        if(nbytes == 0)
                        {
                            //连接关闭了
                            printf("selectserver:socket %d hung up\n", i);
                        }
                        else
                        {
                            perror("recv");
                        }
                        close(i); //关闭
                        FD_CLR(i, &master);  //从主文件描述符列表中删除
                    }
                    else
                    {
                        //我们从一个客户那里得到了一些数据
                        for(j =0; j <= fdmax; j++)
                        {
			    			//发给大家!
                            if(FD_ISSET(j, &master))
                            {
                                //除了监听器和我们自己
                                if(j != listener && j != i)
                                {
                                    if(send(j, buf, nbytes, 0) == -1)
                                    {
                                        perror("send");
                                    }
                                }
                            }
                        }
                    }
                }  //END handle from client
            }  //END got new incoming connection
        }  //END looping through file descriptors
    }  //END for(;;)--and you thought it would never end!
    return 0;
}

client

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <syspes.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>

#define PORT "9090"  //the port client will be connecting to
#define MAXDATASIZE 100  //max number of bytes we can get at once

int sockfd, numbytes;
char buf[MAXDATASIZE];

//get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
	if(sa->sa_family == AF_INET)
	{
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

void *recvMag()
{
    while(1)
    {
        if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
	{
		perror("recv");
		exit(1);
	}
	if(numbytes == 1)
		continue;
	buf[numbytes] = '\0';
	printf("\nreceived:%s\n",buf);
    }
}

int main(int argc, char *argv[])
{
	struct addrinfo hints, *servinfo, *p;
	int rv;
	char s[INET6_ADDRSTRLEN];
    pthread_t t1;
    char mag[MAXDATASIZE];

	if(argc != 2)
	{
		fprintf(stderr, "usage:client hostname\n");
		exit(1);
	}
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
	{
		fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv));
		return 1;
	}
	for(p = servinfo; p != NULL; p = p->ai_next)
	{
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
		{
			perror("client:socket");
			continue;
		}
		if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
		{
			close(sockfd);
			perror("client:connect");
			continue;
		}
		break;
	}
	if(p == NULL)
	{
		fprintf(stderr, "client:failed to connect\n");
		return 2;
	}
	inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s);
	printf("client:connecting to %s\n",s);
	freeaddrinfo(servinfo);

    int err = pthread_create(&t1, NULL, recvMag, NULL);
	if(err != 0)
	{
		printf("receive failed");
		exit(1);
	}
    
	while(1)
	{
        scanf("%s", mag);
        if(send(sockfd, mag, sizeof mag, 0) == -1)
        {
            printf("send failed!\n");
        }
	}
	//close(sockfd);
	return 0;
}

标签:sockaddr,SOCKET,ai,int,hints,实例,应用,include,struct
来源: https://blog.csdn.net/weixin_45189408/article/details/110846473

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有