SOCKET应用实例

SOCKET应用实例

(1)实验代码

非阻塞的多人聊天服务器

1.服务器:

#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;

}

2.客户端

#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;

}

编译运行:
在这里插入图片描述
在这里插入图片描述

在树莓派上运行:
在这里插入图片描述

客户端在ubantu上运行

./client1 192.168.43.99
./client2 192.168.43.99
在这里插入图片描述
在树莓派上运行:
在这里插入图片描述

客户端在ubantu上运行

./client1 192.168.43.99
./client2 192.168.43.99
在这里插入图片描述

小结:两个应用实例是利用Socket实现数据的传输,对于服务端的代码有很多地方基本一致。但是对于后面这个服务器来说,采用的select函数来实现非阻塞。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值