Linux下编写TCP服务器调用的函数顺序为:socket -> bind -> listen -> accept -> recv/send

socket

  参见:http://c.biancheng.net/view/2131.html

  socket函数成功返回文件描述符,失败返回-1

bind

  参见:http://c.biancheng.net/view/2344.html

  需要注意的是,bind函数的第二个参数类型为struct sockaddr *,但用的时候经常传入struct sockaddr_in *类型的参数,具体原因参考网页中有说。并且如果使用的结构是struct sockaddr_in,那么需要包含netinet/in.h。

  struct sockaddr_in结构体中的成员变量参考网页中也有说,需要注意其中的sin_port成员表示端口,需要用htons函数转换,htons函数说明如下:https://blog.csdn.net/zhuguorong11/article/details/52300680

  sin_addr.s_addr也需要用inet_addr函数转换,需要注意的是,给sin_addr.s_addr赋值为0表示绑定本机IP,这也是常用的写法。

  第三个参数类型为socklen_t,直接传入sizeof(struct sockaddr_in)即可。

  返回值:成功返回0,失败返回-1。

  如果程序在acctpt之后非正常退出(ctrl+c),下次bind可能会失败,需要等待一段时间恢复。

listen和accept

  参见:http://c.biancheng.net/view/2345.html

  listen函数成功返回0,失败返回-1

  而accept成功返回一个文件描述符,失败返回-1,这个文件描述符就是可以用recv和send函数通信的文件描述符

  accept函数默认会阻塞,直到有客户端来连接。

  accept函数的第2个参数是输出参数,用来得到客户端的地址和端口

  第三个参数是输入参数,指定的是第二个参数占用的空间大小,即sizeof(struct sockaddr_in)。

  特别注意第三个参数是输入参数而不是输出参数,我在一次测试就遇到了这个问题,我在想如果第三个参数如果是输入,那为什么要传指针类型,因此我将第三个参数指向的值设为0,结果accept运行返回OK了,并且打印第三个参数的值也变成了16。但是打印客户端的IP地址却变成了0.0.0.0,端口号也是0。因此在accept的时候传入的第三个参数指向的值应为sizeof(struct sockaddr_in)。

  accept执行成功后可以用inet_ntoa函数将IP地址以字符串的方式取出, inet_ntoa的函数原型为:char *inet_ntoa(struct in_addr in);

  有一点要特别注意,如果使用了inet_ntoa函数,那么就必须包含arpa/inet.h头文件,否则当你用一个char *类型的变量去接收inet_ntoa函数的返回值时会报一个很奇怪的警告,意思就是inet_ntoa函数返回的是一个int型,但是我用man手册反反复复看了不下5遍,inet_ntoa函数返回的就是char *类型,最后网上找资料发现是必须要包含arpa/inet.h头文件。不过有趣的是,如果我不包含arpa/inet.h头文件,而就用一个int类型的变量来接收inet_ntoa的返回值,结果编译还不会报任何警告和错误,运行程序时还真的能得到一个int类型的值。

  用ntohs取出端口号,ntohs的函数原型为uint16_t ntohs(uint16_t netshort);

recv和send

  recv用于接收数据,函数原型为:ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  sockfd:套接字描述符,注意该描述符不是socket函数返回的描述符,而是accept函数返回的描述符。

  buf:用于存放数据的缓冲区
  len:希望接收的最大字节个数

  flags:一般设置为0,具体描述在man手册中可以看到

  send用于发送数据,函数原型为:ssize_t send(int sockfd, const void *buf, size_t len, int flags);

  sockfd:套接字描述符,注意该描述符不是socket函数返回的描述符,而是accept函数返回的描述符。

  buf:用于存放数据的缓冲区
  len:希望发送的最大字节个数

  flags:一般设置为0,具体描述在man手册中可以看到

  如果客户端断开连接,那么recv函数将不阻塞,返回值为0,可以通过这个方法判断客户端是否断开连接。

  接收数据可以用read函数代替,发送数据也可以用send函数代替。

例程:

tcp_server.c

  1  /**
2 * filename: tcp_server.c
3 * author: Suzkfly
4 * date: 2021-01-22
5 * platform: Ubuntu
6 * 配合windows的网络调试工具使用:
7 * 1、先保证windows与Ubuntu在同一网段且互相能ping通;
8 * 2、在windows下打开网络调试助手,选择协议类型为TCP Client,远程主机地址为
9 * Ubuntu的IP地址,远程主机端口为Ubuntu例程中写的端口,接收设置和发送设
10 * 置都选择ASCLL。
11 * 3、运行Ubuntu下的TCP服务器程序;
12 * 4、网络调试助手上点击“连接”。
13 * 5、连接成功后在网络调试助手上发送数据,在Ubuntu下的终端上能看到,
14 * 在Ubuntu下的终端上输入字符串按回车发送,在windows上的网络调试助手上也
15 * 能看到。
16 */
17 #include <stdio.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <string.h>
21 #include <netinet/in.h>
22 #include <arpa/inet.h>
23 #include <errno.h>
24
25 //#define IP_ADDR "127.0.0.1" /* IP地址 */
26 #define PORT 10000 /* 端口号 */
27
28 int main(int argc, const char *argv[])
29 {
30 int sock_fd = 0, confd = 0;
31 struct sockaddr_in serv_addr; /* 服务器IP(本机IP) */
32 struct sockaddr_in client_addr; /* 客户端IP(连接者IP) */
33 socklen_t addr_len = sizeof(struct sockaddr_in);
34 int ret = 0; /* 用于接收函数返回值 */
35 int pid = 0;
36 char buf[128] = { 0 }; /* 用于存放数据的缓冲区 */
37 int len = 0; /* 发送和接收数据的长度 */
38
39 /* 创建TCP套接字 */
40 sock_fd = socket(AF_INET, SOCK_STREAM, 0);
41
42 /* 将套接字与IP和端口绑定 */
43 memset(&serv_addr, 0, sizeof(struct sockaddr_in));
44 serv_addr.sin_family = AF_INET;
45 //serv_addr.sin_addr.s_addr = inet_addr(IP_ADDR); /* 绑定IP */
46 serv_addr.sin_addr.s_addr = 0; /* 绑定0就是绑定自己 */
47 serv_addr.sin_port = htons(PORT); /* 端口号 */
48 ret = bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr_in));
49 if (ret == 0) {
50 printf("bind ok\n");
51 } else {
52 printf("bind failed\n");
53 close(sock_fd);
54 return 0;
55 }
56
57 /* 让套接字进入被动监听状态 */
58 ret = listen(sock_fd, 10);
59 if (ret == 0) {
60 printf("listen ok\n");
61 } else {
62 printf("listen failed\n");
63 close(sock_fd);
64 return 0;
65 }
66
67 re_connect:
68
69 /* 接收客户端请求(阻塞) */
70 memset(&client_addr, 0, sizeof(client_addr));
71 printf("accept...\n");
72 confd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len);
73 if (confd > 0) {
74 printf("accept ok\n");
75 } else {
76 printf("accept failed\n");
77 close(sock_fd);
78 return 0;
79 }
80
81 /* 打印客户端信息 */
82 printf("addr_len = %d\n", addr_len);
83 printf("Client IP: %s\n", inet_ntoa(client_addr.sin_addr)); /* IP地址 */
84 printf("Client Port:%d\n", ntohs(client_addr.sin_port)); /* 端口号 */
85
86 pid = fork();
87
88 if (pid > 0) { /* 接收数据 */
89 while (1) {
90 memset(buf, 0, sizeof(buf));
91 len = recv(confd, buf, sizeof(buf), 0);
92 //len = read(confd, buf, sizeof(buf));
93 if (len == 0) { /* 如果recv返回0,则表示远端断开连接 */
94 goto re_connect;
95 }
96 printf("len = %d\n", len);
97 printf("data: %s\n", buf);
98 }
99 } else if (pid == 0) {
100 while (1) { /* 发送数据 */
101 memset(buf, 0, sizeof(buf));
102 scanf("%s", buf);
103 len = send(confd, buf, strlen(buf), 0);
104 //len = write(confd, buf, strlen(buf));
105 }
106 }
107 }

该测试程序有一个BUG,如果客户端断开连接,那么recv返回0,则会跳转到代码第67行重新连接,如果连接成功,则在第86行又会调用fork函数,创建出一个新的进程。在测试时发现,如果客户端断开连接并重新连接后的一端时间内,在服务器终端中输入的数据在网络调试助手中收不到。

网络调试助手设置如下:

注意:

例程中如果使用windows的网络调试工具,如果在绑定的时候指定回环网卡,则在windows上的网络调试助手连接不上。

  

TCP服务器程序的更多相关文章

  1. TCP客户端程序

    TCP客户端程序的函数调用顺序为:socket -> connect -> send/recv socket.send和recv函数在TCP服务器程序中已经说过了,这里就不赘述了. con ...

  2. UNIX网络编程---TCP客户/服务器程序示例(五)

    一.概述 客户从标准输入读入一行文本,并写给服务器 服务器从网络输入读入这行文本,并回射给客户 客户从网络输入读入这行回射文本,并显示在标准输出上 二.TCP回射服务器程序:main函数 这里给了函数 ...

  3. 在向服务器发送请求时发生传输级错误。 (provider: TCP 提供程序, error: 0 - 远程主机强迫关闭了一个现有的连接。)

    用VS2005+SQLSERVER2008开发C/S的程序,程序上线运行一段时间之后发现在某些功能偶尔出现如下的错误: 在向服务器发送请求时发生传输级错误. (provider: TCP 提供程序, ...

  4. UNIX网络编程——使用select函数的TCP和UDP回射服务器程序

    服务器程序: #include <sys/wait.h> #include <string.h> #include <string.h> #include < ...

  5. 【Unix网络编程】 chapter5 TCP客户,服务器程序实例

    chapter5 5.1 概述 5.2 TCP回射服务器程序:main函数 int main(int argc, char **argv) { int listenfd,connfd; pid_t c ...

  6. UNIX网络编程 第5章 TCP客户/服务器程序示例

    UNIX网络编程 第5章 TCP客户/服务器程序示例

  7. TCP客户/服务器程序实例——回射服务器

    目录 客户/服务器程序源码 POSIX信号处理 POSIX信号语义 处理SIGCHLD信号 处理僵死进程 处理被中断的系统调用 wait和waitpid函数 wait和waitpid函数的区别 网络编 ...

  8. System.Data.SqlClient.SqlException: 在向服务器发送请求时发生传输级错误。 (provider: TCP 提供程序, error: 0 - 远程主机强迫关闭了一个现有的连接。) .

    今天使用sql server 2008 R2管理器,进行SQL查询时,频率非常高的报错: System.Data.SqlClient.SqlException: 在向服务器发送请求时发生传输级错误. ...

  9. 第四章 基本TCP套接字编程 第五章 TCP客户/服务器程序实例

    TCP客户与服务器进程之间发生的重大事件时间表 TCP服务器 socket() --- bind() --- listen() --- accept() --- read() --- write -- ...

随机推荐

  1. [BUUOJ]刮开有奖reverse

    刮开有奖 这是一个赌博程序,快去赚钱吧!!!!!!!!!!!!!!!!!!!!!!!!!!!(在编辑框中的输入值,即为flag,提交即可) 注意:得到的 flag 请包上 flag{} 提交 1.查壳 ...

  2. Hibernate实现对数据的CRUD

    今天主要去看公司的老框架, CRUD用的较多,所以总结一下步骤,以免忘记的时候温习 回顾 JDBC 工作过程: 加载驱动 建立连接 定义sql,发生sql语句 执行sql语句获得执行结果 处理返回结果 ...

  3. Python爬虫:爬取喜马拉雅音频数据详解

    前言 喜马拉雅是专业的音频分享平台,汇集了有声小说,有声读物,有声书,FM电台,儿童睡前故事,相声小品,鬼故事等数亿条音频,我最喜欢听民间故事和德云社相声集,你呢? 今天带大家爬取喜马拉雅音频数据,一 ...

  4. Python朗读excel中的英文单词

    安装win32com的时候出现了诸多问题,直接贴代码: 1 ''' 2 #利用python朗读excel里面的单词 3 ''' 4 5 #开始导入所需库 6 import xlrd 7 from bs ...

  5. postgresql 创建分表

    划分指的是将逻辑上的一个大表分成一些小的物理上的片.划分有很多益处: 1.在某些情况下查询性能能够显著提升,特别是当那些访问压力大的行在一个分区或者少数几个分区时.划分可以取代索引的主导列.减小索引尺 ...

  6. 计算-服务器最大并发量-http协议请求-以webSphere服务器为例-考虑线程池

    请求的处理流程 广域网上有大量的并发用户同时访问Web服务器,Web服务器传递请求给应用服务器(Web容器),Web容器传递请求给EJB容器,然后EJB容器发送数据库连接请求给数据库. 请求的处理流程 ...

  7. ES6模板字符串及字符串的扩展方法

    一.ES6模板字符串 传统定义字符串的方式是: const str='hello es2015,this is a string' ES6新增了一种定义字符串的方式用反引号进行标识 const str ...

  8. Redis学习之路(三)常用命令总结

    一.集群 查看Redis集群有多少个库 192.168.200.100:7001> config get databases 1) "databases" 2) " ...

  9. 一图看懂Actor Typed

    引言 朋友看罢我之前整理的<Akka Typed 官方文档之随手记>,一人用了诗歌<长城长>作为回赠,另一人则要求推出简化版本.于是抽空整理了几张思维导图,并且用了一些不太恰当 ...

  10. Netty学习之IO模型

    目录 1.1 同步.异步.阻塞.非阻塞     同步 VS 异步         同步         异步     阻塞 VS 非阻塞         阻塞         非阻塞     举例   ...