目录

  • 一、面向连接的流式套接字 C/S
  • 二、非阻塞的多人聊天服务器
  • 三、其他优秀博主

一、面向连接的流式套接字 C/S

服务器代码

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

二、非阻塞的多人聊天服务器

服务器代码

#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,belowint 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 onceint 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;
}

三、其他优秀博主

https://blog.csdn.net/xianyudewo/article/details/110647187

(网络编程)SOCKET应用实例相关推荐

  1. Android网络编程Socket【实例解析】

    Socket 事实上和JavaWeb 里面的Socket一模一样 建立客服端,server端,server开一个port供客服端訪问 第一步创建server端:(这里把为了便于解说.把server端, ...

  2. Linux 网络编程——socket 网络编程

    文章目录 一.网络基础 TCP/UDP对比 TCP/IP协议族体系 socket IP地址 IP地址转化API inet_addr() inet_aton() inet_ntoa() inet_pto ...

  3. Python网络编程(Socket)

    Python网络编程(Socket) Python提供了两个访问级别的网络服务.在一个较低的水平,您可以访问底层操作系统的基本套接字支持,允许你实现面向连接和无连接协议的客户端和服务器 Python有 ...

  4. Java网络编程 Socket、ServerSocket 详解,方法介绍及完整代码示例

    Java网络编程 Socket.ServerSocket 详解,方法介绍及完整代码示例 概念 什么是网络编程? 网络编程是指编写运行在多个设备(计算机)的程序,这些设备通过网络连接起来.当这些通过网络 ...

  5. 网络编程socket之connect函数

    网络编程socket api存在一批核心接口,而这一批核心接口就是几个看似简单的函数,尽管实际上这些函数没有一个是简单.connect函数就是这些核心接口的一个函数,它完成主动连接的过程. conne ...

  6. Python网络编程socket

    网络编程之socket 看到本篇文章的题目是不是很疑惑,what is this?,不要着急,但是记住一说网络编程,你就想socket,socket是实现网络编程的工具,那么什么是socket,什么是 ...

  7. 树莓派 Python 网络编程 (Socket入门)

    树莓派  Python 网络编程 (Socket入门) 什么是 Socket? Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络 ...

  8. 树莓派:入门(基础配置、GPIO、网络编程 Socket)

    树莓派在创客中越来越发挥重要的作用,树莓派的强大兼用性和功能丰富,得到 DIY 朋友的青睐.带大家认识目前最新的树莓派 3B+,从零基础到入门,到动手做有趣的应用. 本场 Chat 首先会带领大家入门 ...

  9. java网络编程socket\server\TCP笔记(转)

    java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04|  分类: Socket |  标签:java  |举报|字号 订阅 1 TCP的开销 a  连接 ...

  10. 网络编程socket之accept函数

    网络编程socket之accept函数 摘要:对于服务器编程中最重要的一步等待并接受客户的连接,那么这一步在编程中如何完成,accept函数就是完成这一步的.它从内核中取出已经建立的客户连接,然后把这 ...

最新文章

  1. Nature:拟南芥根系微生物组的结构
  2. python asyncio 使用方法
  3. Apple导出p12证书 导出证书为p12 Apple开发
  4. 【Vegas原创】GridView设定DataFormatString属性失效的解决方法
  5. 把技术卖给不懂技术的人
  6. http://blog.chinaunix.net/uid-25082381-id-3242162.html
  7. 3使用Jsoup解析Java中HTML文件的示例
  8. 19c 新特性: Hint Usage Reports详解
  9. dijkstra邻接表_[力扣743] 带权邻接表的单源最短路
  10. 7个开源的SNS社交网络平台
  11. texlive写论文源代码_基于中国人民大学LaTeX论文模板毕业论文,课程研究生硕士本科设计,ppt答辩,外文翻译程序源代码下载...
  12. 关于卸载流氓软件,自己试错的一些结论
  13. c4d流体插件_C4D流体模拟插件 NextLimit RealFlow 2.6.5.0095 Win已注册版
  14. 子域名收集 -- teemo
  15. matlab绘制叮当猫
  16. Linux——scp的用法
  17. laravel 导出Excel,多维表头
  18. RecyclerView Adapter 优雅封装搞定所有列表
  19. Debian 配置RTL8723BU连接wifi网络
  20. 电子元器件[1]——晶振

热门文章

  1. tar zip 打包相关操作
  2. [C++] [FLTK] 很久以前写的FLTK计算器
  3. 详解Python正则表达式基础操作
  4. DHI Mike 后处理工具——污染带面积、长度、宽度统计工具
  5. 计算机软考里面的英语试题,计算机软考模拟试题
  6. 各纬度气候分布图_气候分布图有纬度
  7. DLL注入explorer.exe进程
  8. c语言ascii码16进制,C语言附录ASCII码表.doc
  9. maven下载及安装教程(保姆及教程)
  10. 11(1)-AirSim+四旋翼仿真-人工势场法动态避障