• Linux系统下建立Socket聊天服务器


    目录

    1.服务器结构

    2.各模块函数

    2.1 socket函数 

    2.2 bind函数

    2.3 Listen函数

    2.4 accept函数

    2.5 接收发送函数

    2.6 close函数

    2.7 connect函数

    3 代码段

    3.1 服务器代码


    1.服务器结构

    使用socket的API函数编写服务端和客户端程序的步骤图示:

    2.各模块函数

    服务器:

    2.1 socket函数 

    使用socket会建立一个服务器文件描述符

    • 成功: 返回一个大于0的文件描述符
    • 失败: 返回-1, 并设置errno
    int socket(int domain, int type, int protocol);
    

    domain: 协议版本

    1. AF_INET IPV4
    2. AF_INET6 IPV6
    3. AF_UNIX AF_LOCAL本地套接字使用

    type:协议类型

    1. SOCK_STREAM 流式, 默认使用的协议是TCP协议
    2. SOCK_DGRAM 报式, 默认使用的是UDP协议

    protocal:

    一般填0, 表示使用对应类型的默认协议.

    2.2 bind函数

    成功: 返回0
    失败: 返回-1, 并设置errno

     int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    函数描述: 将socket文件描述符和IP,PORT绑定。

    sockfd为socket的返回值,文件描述符

    struct sockaddr* addr结构体可以用下面的

    1. struct sockaddr_in serv;
    2. serv.sin_family = AF_INET;//选择使用的网络协议
    3. serv.sin_port = htons(8888);//绑定本机端口,通常占2字节。注意:端口号尽量不要填1024以前的数字,因为可以被系统预留了。
    4. serv.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY: 表示使用本机任意有效的可用IP
    5. 如果想自己指定ip地址作为服务器连接就需要这个:
    6. inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
    7. 或者inet_aton("127.0.0.1", &serv.sin_addr);
    8. 或者这个:addr.sin_addr.s_addr = inet_addr("192.168.239.1");

    127.0.0.1是回送地址,指本地机,一般用来测试使用。 

     同时在使用addr时,先对其进行清空memset。

    “端口号所谓的端口,就好像是门牌号一样,客户端可以通过ip地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。”
      你可能对出现的htons()、htonl和inet_pton()不知道是何意,在网络传输中,不同的机器端不一样,有的机器是大端有的机器是小端。这些函数是为了帮助你在传输网络数据的时候统一格式。(没有超过一个字节不需要转)

    1. 大端: 低位地址存放高位数据, 高位地址存放低位数据(也叫网络字节序)
    2. 小端: 低位地址存放低位数据, 高位地址存放高位数据(也叫小端字节序)

    网络中传输使用的是大端法,如果机器使用的是小端,则需要进行大小端的转换。
      下面4个函数就是进行大小端转换的函数:

    1.   #include
    2.        uint32_t htonl(uint32_t hostlong);
    3.        uint16_t htons(uint16_t hostshort);
    4.        uint32_t ntohl(uint32_t netlong);
    5.        uint16_t ntohs(uint16_t netshort);

    函数名的h表示主机host, n表示网络network, s表示short, l表示long
    上述的几个函数, 如果本来不需要转换函数内部就不会做转换.

    IP地址转换函数:

    p->表示点分十进制的字符串形式
    to->到
    n->表示network网络

    int inet_pton(int af, const char *src, void *dst);

    函数说明: 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)
    参数说明:

    1. af: AF_INET
    2. src: 字符串形式的点分十进制的IP地址
    3. dst: 存放转换后的变量的地址

    如192.168.232.145, 先将4个正数分别转换为16进制数,
      192—>0xC0 168—>0xA8 232—>0xE8 145—>0x91
      最后按照大端字节序存放: 0x91E8A8C0, 这个就是4字节的整形值.

    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    


    函数说明: 网络IP转换为字符串形式的点分十进制的IP
    参数说明:

    1. af: AF_INET
    2. src: 网络的十六进制的IP地址
    3. dst: 转换后的IP地址,一般为字符串数组
    4. size: dst的长度
    1. 成功--返回执行dst的指针
    2. 失败--返回NULL, 并设置errno

    如 IP地址为010aa8c0, 转换为点分十进制的格式:
      01---->1 0a---->10 a8---->168 c0---->192
      由于从网络中的IP地址是高端模式, 所以转换为点分十进制后应该为: 192.168.10.1

    2.3 Listen函数

    int listen(int sockfd, int backlog);

    成功: 返回0 失败: 返回-1, 并设置errno 

    函数描述: 将套接字由主动态变为被动态,也就是设置为监听文件描述符。

    参数说明:

    1. sockfd: 调用socket函数返回的文件描述符
    2. backlog: 同时请求连接的最大个数(还未建立连接) 设置为6/7
    3. 注意:在linux系统中,这里代表全连接队列(已连接队列)的数量。在unix系统种,这里代表全连接队列(已连接队列)+ 半连

    2.4 accept函数

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	

    函数说明:获得一个连接, 若当前没有连接则会阻塞等待.

    1. sockfd: 调用socket函数返回的文件描述符
    2. addr: 传出参数, 保存客户端的地址信息。如果不关心可以传NULL
    3. addrlen: 传入传出参数, addr变量所占内存空间大小,这个传出的时候会告诉我们填充的多少的内容。如果不关心可以传NULL

    成功: 返回一个新的文件描述符,用于和客户端通信             失败: 返回-1, 并设置errno值.

    accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞。从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)。

    2.5 接收发送函数

    接下来就可以使用write和read函数进行读写操作了。除了使用read/write函数以外, 还可以使用recv和send函数。

    1. ssize_t read(int fd, void *buf, size_t count);
    2. ssize_t write(int fd, const void *buf, size_t count);
    3. ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    4. ssize_t send(int sockfd, const void *buf, size_t len, int flags);    
    5. //对应recv和send这两个函数flags直接填0就可以了.

    fd为accept返回的fd,count为字节,flag写0

    注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞.
     

    2.6 close函数

      最后通讯完之后记得close()文件描述符,关闭文件描述符后就断开了连接,就从已连接队列里面去掉了.。

    2.7 connect函数

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    函数说明: 连接服务器,client.c使用connect函数前应该先使用socket函数得到文件描述符fd。
    函数参数:

    addr设置为服务端一样的就行,进行传入

    1. sockfd: 调用socket函数返回的文件描述符
    2. addr: 服务端的地址信息
    3. addrlen: addr变量的内存大小 用sizeof

    返回值:

    1. 成功: 返回0
    2. 失败: 返回-1, 并设置errno值

    主要用于客户端连接,客户端不需要绑定端口、ip什么的,因为只要能连上然后传输接收数据就行。
    然后直接用sockfd进行读写就行。

    3 代码段

    3.1 服务器代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main(void)
    9. {
    10. int s_fd,ss_fd,nread,len;
    11. char buf[32];
    12. char msg[32];
    13. struct sockaddr_in s_ddr; //build server msg
    14. struct sockaddr_in c_ddr; //save clinet msg
    15. s_fd= socket(AF_INET, SOCK_STREAM, 0);//1.build a soket specified
    16. if(s_fd==-1){
    17. perror("error is");
    18. }
    19. //2.build all bind
    20. s_ddr.sin_family=AF_INET;
    21. s_ddr.sin_port=htons(8880);
    22. s_ddr.sin_addr.s_addr=htonl(INADDR_ANY);
    23. //give the bind
    24. bind(s_fd,(struct sockaddr *)&s_ddr,sizeof(s_ddr));
    25. //3.waite for client
    26. listen(s_fd,8);
    27. //4.accept come and connect for once
    28. len=sizeof(c_ddr);
    29. while(1){ //这里用while1是为了一直可以被连接
    30. ss_fd=accept(s_fd,(struct sockaddr *)&c_ddr,&len);
    31. printf("conect succese!==========\r\n");
    32. //5.read from connect ss_fd
    33. if(fork()==0){ //创建一个子进程(服务员)去接待client
    34. if(fork()==0){ //fork is zero is child pid //创建一个子进程去等待发送
    35. //5.1 send
    36. while(1){
    37. memset(msg,0,32);
    38. printf("input:");
    39. gets(msg);
    40. send(ss_fd,msg,32,0);
    41. }
    42. }
    43. //5.2 read //在父进程中等待接收数据
    44. while(1){
    45. memset(buf,'\0',32);
    46. nread=read(ss_fd,&buf,32);
    47. printf("server receved :%s \r\n",buf);
    48. }
    49. }
    50. }
    51. close(ss_fd);
    52. close(s_fd);
    53. return 0;
    54. }

    3.2 客户端代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main(int argc,char *argv[])
    8. {
    9. int flag,s_fd,n_read;
    10. struct sockaddr_in c_ddr;
    11. char readbuf[32];
    12. char msg[32];
    13. //1.build socket
    14. s_fd=socket(AF_INET,SOCK_STREAM,0);
    15. //2.0 prepare server addr
    16. memset(&c_ddr,0,sizeof(c_ddr)); //clear c_ddr
    17. c_ddr.sin_family=AF_INET;
    18. c_ddr.sin_port=htons(8880);
    19. inet_aton("192.168.102.141",&c_ddr.sin_addr);
    20. //2.connect server get s_fd
    21. if(connect(s_fd,(struct sockaddr *)&c_ddr,sizeof(c_ddr))==-1){
    22. perror("error");
    23. }
    24. printf("connect success==============\r\n");
    25. while(1){ //while1父进程一直等待读数据
    26. //recv will block
    27. memset(readbuf,0,32);
    28. read(s_fd,readbuf,32);
    29. printf("form server:%s\r\n",readbuf);
    30. //send
    31. if(fork()==0){ //fork is zero is child pid //子进程一直(while1)等待发数据
    32. while(1){
    33. memset(msg,0,32);
    34. printf("input :::::");
    35. gets(msg);
    36. send(s_fd,msg,32,0);
    37. }
    38. }
    39. }
    40. close(s_fd);
    41. return 0;
    42. }

    参考博文:

    Linux环境下socket服务器搭建_socket搭建linux_master cat的博客-CSDN博客

    socket编程 服务器_socket 服务器_不爱学习的王小二的博客-CSDN博客

  • 相关阅读:
    JavaWeb基础3——Maven&MyBatis
    【linux学习】文本文件编辑命令
    【C++】双向带头循环链表~~看似结构最复杂但是实现超简单
    C++学习——C++中const的新花样
    Javascript【触屏事件、移动端常见特效、移动端常用开发插件、移动端常用开发框架】
    HandlerAdapter具有什么功能呢?
    Windows下 开机自启动jar包
    创纪录的1亿RPS DDoS攻击利用HTTP/2快速重置漏洞
    Python3出现的Error总结
    第3周学习:ResNet+ResNeXt
  • 原文地址:https://blog.csdn.net/m0_67794575/article/details/132621529