socket编程实战

面向连接的流接套接字

服务端

代码:

#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函数原型:
	pid_t waitpid(pid_t pid,int *status,int options)
	作用:暂时停止目前进程的执行,直到有信号来到或子进程结束
	参数说明:
	pid:当pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程
	运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
	pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作
	用一模一样。pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加
	入了别的进程组,waitpid不会对它做任何理睬。pid<-1时,等待一个指定进程
	组中的任何子进程,这个进程组的ID等于pid的绝对值。
	options:目前只要WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用
	函数的返回值:
	当正常返回的时候,waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
	如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,
	waitpid就会出错返回,这时errno被设置为ECHILD;
	*/
	while(waitpid(-1, NULL, WNOHANG) > 0); 
}
// get sockaddr, IPv4 or IPv6(获取IP地址): 
/*struct sockaddr {  
     sa_family_t sin_family;//地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   }; 
   如果地址是IPv4,返回32位的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) 
{ 
	int sockfd, new_fd; // listen on sock_fd, new connection on new_fd 
	/*struct addrinfo {
		int ai_flags;//指示在getaddrinfo函数中使用的选项的标志。
		int ai_family;
		int ai_socktype;
		int ai_protocol;
		size_t ai_addrlen;//缓冲区的长度(以字节为单位)
		char *ai_canonname;//主机的规范名称
		struct sockaddr *ai_addr;//向 sockaddr 结构的指针。每个返回的addrinfo结构中的ai_addr成员指向一个填充的套接字地址结构
		struct addrinfo *ai_next;//指向链表中下一个结构的指针。此参数在链接列表的最后一个addrinfo结构中设置为NULL。
	} 
	*/
	struct addrinfo hints, *servinfo, *p; 
	/*struct sockaddr_storage
	 {
	     sa_family_t ss_family;      //地址族
	     __ss_aligntype __ss_align;  //Force desired alignment.  
	     char __ss_padding[_SS_PADSIZE];
	};*/
	struct sockaddr_storage their_addr; // connector's address information 
	socklen_t sin_size; 
	/*struct sigaction {
	    void (*sa_handler)(int);//新的信号处理函数
	    void (*sa_sigaction)(int, siginfo_t *, void *);
	    sigset_t sa_mask;//设置在处理该信号时暂时将sa_mask 指定的信号集搁置
	    int sa_flags;//设置信号处理的其他相关操作 
	    void (*sa_restorer)(void);
	}*/
	struct sigaction sa; 
	int yes=1;
	char s[INET6_ADDRSTRLEN]; 
	int rv; 
	/* memset()函数原型:
	extern void *memset(void *buffer, int c, int count) 
	参数说明:
	buffer:为指针或是数组,
    c:是赋给buffer的值,
    count:是buffer的长度
    作用:
    将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。socket中多用于清空数组。
    */
	memset(&hints, 0, sizeof hints); 
	hints.ai_family = AF_UNSPEC; 
	hints.ai_socktype = SOCK_STREAM; 
	hints.ai_flags = AI_PASSIVE; // use my IP 
	/*getaddrinfo函数原型:
	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——出错
		*/
	if ((rv = getaddrinfo(NULL, 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("server: socket"); 
			continue; 
		}
		/*setsockopt函数是用于任意类型、任意状态套接口的设置选项值,即设置套接口的选项
        setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one))的原型:
        int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
        sockfd:标识一个套接口的描述字。
        level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6等。
        optname:需设置的选项。
        optval:指针,指向存放选项值的缓冲区。
        optlen:optval缓冲区长度。
        IP_HDRINCL:如果是TRUE,IP头就会随即将发送的数据一起提交,并从读取
        的数据中返回
        */
		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) 
		{ 
			perror("setsockopt"); 
			exit(1); 
		}
		/*通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)。
		bind函数原型:
		int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
		参数说明:
		sockfd:已经建立的socket编号(描述符);
		my_addr:一个指向sockaddr结构体类型的指针;
		addrlen:my_addr结构的长度,可以用sizeof操作符获得
		*/
		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) 
		{ 
			close(sockfd); 
			perror("server: bind"); 
			continue; 
		}
		break; 
	}
	if (p == NULL) 
	{ 
		fprintf(stderr, "server: failed to bind\n"); 
		return 2; 
	}
	freeaddrinfo(servinfo); // all done with this structure 
	/*创建一个套接口并监听申请的连接.
	listen函数原型:
	int listen( int sockfd, int backlog);
	参数说明:
	sockfd:用于标识一个已捆绑未连接套接口的描述字。
	backlog:等待连接队列的最大长度*/
	if (listen(sockfd, BACKLOG) == -1) 
	{ 
		perror("listen"); 
		exit(1); 
	}
	sa.sa_handler = sigchld_handler; // reap all dead processes 
	//sigemptyset 函数初始化信号集合set,将set 设置为空.
	sigemptyset(&sa.sa_mask); 
	sa.sa_flags = SA_RESTART; 
	/*sigaction函数用来查询和设置信号处理方式
	函数原型:
	int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact)
	*/
	if (sigaction(SIGCHLD, &sa, NULL) == -1) 
	{ 
		perror("sigaction"); 
		exit(1);
	} 
	printf("server: waiting for connections...\n"); 
	//循环接收客户端的连接
	while(1) 
	{ // main accept() loop 
		sin_size = sizeof their_addr; 
		/*一个套接口接受的一个连接
		accept函数原型:
		SOCKET accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	参数说明:
	sockfd:套接字描述符,该套接口在listen()后监听连接。
	addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
	addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。*/
		new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); 
		if (new_fd == -1) 
		{ 
			perror("accept"); 
			continue; 
		}
		/*inet_ntop函数原型:
		const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);   
		作用:
		将数值格式转化为点分十进制的ip地址格式
        返回值:若成功则为指向结构的指针,若出错则为NULL*/
		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; 
}


客户端:

在这里插入代码#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; 
	int rv; 
	char s[INET6_ADDRSTRLEN]; 
	//若输入的命令行参数(包括命令本身)不等于2,就输出错误信息,并退出程序。
	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); // all done with this structure 
	if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) 
	{ 
		perror("recv"); 
		exit(1);
	}
	buf[numbytes] = '\0'; 
	printf("client: received '%s'\n",buf); 
	close(sockfd); 
	return 0; 
}

编译:

gcc server1.c -o server1
gcc client1.c -o client1

server1程序位于树莓派上
client1程序位于Ubuntu上
运行:
在这里插入图片描述

非阻塞多人聊天服务器

服务器
#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:
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() 的临时文件描述符列表
    int fdmax;  //最大文件
    int listener;  //监听套接字
   
    struct sockaddr_storage remoteaddr;  //客户端地址,该数据结构是用于存储套接字地址信息
    socklen_t addrlen;

    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);  //将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。socket中多用于清空数组。此处是将地址信息置零。
    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;
        }
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
        /*
        bind 函数:用于连接的数据报或流类套接口,进行绑定
        */
        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;
}


客户端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.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()
{
	/*
	int recv( SOCKET s,char FAR *buf,int len,int flags);   
	recv函数从TCP连接的另一端接收数据
	第一个参数指定接收端套接字描述符;
	第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
	第三个参数指明buf的长度;
	第四个参数一般置0。
	*/
    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");
        }
	}
	return 0;
}


编译:
gcc server2.c -o server2
gcc -pthread client2.c -o client2
运行:
服务器在树莓派

./server3

在这里插入图片描述
客户端运行在Ubuntu

./client1 192.168.43.99
./client2 192.168.43.99

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值