TCP服务器程序
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服务器程序的更多相关文章
- TCP客户端程序
TCP客户端程序的函数调用顺序为:socket -> connect -> send/recv socket.send和recv函数在TCP服务器程序中已经说过了,这里就不赘述了. con ...
- UNIX网络编程---TCP客户/服务器程序示例(五)
一.概述 客户从标准输入读入一行文本,并写给服务器 服务器从网络输入读入这行文本,并回射给客户 客户从网络输入读入这行回射文本,并显示在标准输出上 二.TCP回射服务器程序:main函数 这里给了函数 ...
- 在向服务器发送请求时发生传输级错误。 (provider: TCP 提供程序, error: 0 - 远程主机强迫关闭了一个现有的连接。)
用VS2005+SQLSERVER2008开发C/S的程序,程序上线运行一段时间之后发现在某些功能偶尔出现如下的错误: 在向服务器发送请求时发生传输级错误. (provider: TCP 提供程序, ...
- UNIX网络编程——使用select函数的TCP和UDP回射服务器程序
服务器程序: #include <sys/wait.h> #include <string.h> #include <string.h> #include < ...
- 【Unix网络编程】 chapter5 TCP客户,服务器程序实例
chapter5 5.1 概述 5.2 TCP回射服务器程序:main函数 int main(int argc, char **argv) { int listenfd,connfd; pid_t c ...
- UNIX网络编程 第5章 TCP客户/服务器程序示例
UNIX网络编程 第5章 TCP客户/服务器程序示例
- TCP客户/服务器程序实例——回射服务器
目录 客户/服务器程序源码 POSIX信号处理 POSIX信号语义 处理SIGCHLD信号 处理僵死进程 处理被中断的系统调用 wait和waitpid函数 wait和waitpid函数的区别 网络编 ...
- System.Data.SqlClient.SqlException: 在向服务器发送请求时发生传输级错误。 (provider: TCP 提供程序, error: 0 - 远程主机强迫关闭了一个现有的连接。) .
今天使用sql server 2008 R2管理器,进行SQL查询时,频率非常高的报错: System.Data.SqlClient.SqlException: 在向服务器发送请求时发生传输级错误. ...
- 第四章 基本TCP套接字编程 第五章 TCP客户/服务器程序实例
TCP客户与服务器进程之间发生的重大事件时间表 TCP服务器 socket() --- bind() --- listen() --- accept() --- read() --- write -- ...
随机推荐
- Docker 快速部署 Django项目到云服务器
项目结构: 1,dockerfile FROM python:3.7 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY pip.conf /roo ...
- Maven大全
Maven 命令 mvn clean compile -Dmaven.test.skip=true 编译代码,检查代码安全性 Maven 注解 用maven管理库依赖,有个好处就是连同库的依赖的全部j ...
- Ext CheckBoxGroup使用
一.效果图展示 我这里主要是为了实现选择周期时间.如周一.周二.周三等 二.界面界面代码 下面就是我实现的代码,包含了界面.数据处理.回填数据等.可能架构的方式,您的代码和我的代码有差异,但是大体就是 ...
- idea2020 没有 Autoscroll from Source
2018版本: 2020版本: 最后在官网的网站中找到了解决方案,原来是改名了: 网址:https://intellij-support.jetbrains.com/hc/en-us/communit ...
- Idea创建Maven项目时出现Failed to execute goal org.apache.maven.plugins:maven-archetype-plugin:3.0.1错误
如果Maven中用的jre用的是idea中自带的,但是环境变量JAVA_HOME配置的是自己的jdk,那么就会出现 解决方法是到settiing中把jre改成自己的jdk中的jre 经过尝试,问题解决
- jsp文件导包
可以在一个页面中用上多个<% @ page %>指令,但是其中的属性只能用一次,不过也有个例外,那就是import属性.因为import属性和Java中的import语句差不多(参照Jav ...
- Node.js gulp安装与使用出现问题与解决
此前使用了最新的4.0之后的gulp版本,出现了一系列的问题. 于是想换回3.9版本 (一)本地与全局都需要安装gulp ①首先卸载原来版本 npm uninstall gulp ②本地与全局分别安装 ...
- python之random 、os 、sys 模块
一.random模块 import random print(random.random())#(0,1)----float 大于0且小于1之间的小数 print(random.randint(1,3 ...
- 浅析 MVC Pattern
一.前言 最近做CAD插件相关的工作,用到了一些模式,解决对应场景的问题. 比如插件的运行实例上使用Singleton.实例内部使用了MVC(Strategy and Observer ). 针对CA ...
- [leetcode] 周赛 223
比赛题目:https://leetcode-cn.com/contest/weekly-contest-223/. 解码异或后的数组 题目:1720. 解码异或后的数组. 还记得数列求和的「累加法」? ...