基于TCP通信的简单服务端和客户端程序
2018-01-29 20:38
676 查看
背景
因为最近在研究网络相关的东西,因此经常要写程序做实验来验证。主要是TCP通信,因此就写了个简单的基于TCP通信的小程序,方便以后要使用的时候能直接复用,省的还要各种谷歌、百度。功能介绍
写的很简单,实现的就是客户端读取键盘输入,发送给服务端,服务端打印出该输入。因为只研究TCP通信原理,就没有再做其他的多线程并发之类的功能。
代码
1、先看服务端代码/*服务端TCP程序一般流程 *1、创建socket *2、绑定端口和ip *3、监听socket *4、接收客户端的请求 *5、从缓冲区中读取数据 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <arpa/inet.h> #include <netinet/in.h> #define PORT 5000 #define IP "192.168.0.106" #define BACKLOG 5 int main() { int listen_fd; int accept_fd; char buf[1024] = {0};//读写缓冲区 struct sockaddr_in server_addr; //需要获取客户端相关信息 struct sockaddr_in client_addr; socklen_t client_len; client_len = sizeof(client_addr); //创建socket if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } //绑定端口和ip server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr(IP); if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("bind"); return 2; } //监听socket if (listen(listen_fd, BACKLOG) < 0) { perror("listen"); return 3; } while(1) { //接收客户端的请求 if ((accept_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len)) < 0) { perror("accept"); return 4; } //打印客户端信息 printf("connected with ip: %s: port:%d\n", inet_ntop(AF_INET, &client_addr.sin_addr, buf, 1024), ntohs(client_addr.sin_port)); while (1) { memset(buf, 0, sizeof(buf)); //从缓冲区中读取数据 ssize_t size = read(accept_fd, buf, sizeof(buf) - 1); if (size > 0) printf("client send: %s\n", buf); else if (size == 0) { printf("read done!\n"); close(accept_fd); break; } else { perror("read"); close(accept_fd); break; } } } close(listen_fd); return 0; }
2、然后是客户端代码
/*客户端TCP程序一般流程 *1、创建socket *2、向服务端发起连接 *3、向缓冲区中写入数据 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <arpa/inet.h> #include <netinet/in.h> #define PORT 5000 #define IP "192.168.0.106" int main() { int client_fd; char buf[1024] = {0};//读写缓冲区 struct sockaddr_in client_addr; ssize_t size; //创建socket if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } //填充ip端口信息 client_addr.sin_family = AF_INET; client_addr.sin_port = htons(PORT); client_addr.sin_addr.s_addr = inet_addr(IP); //向服务端发起连接 if (connect(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) { perror("connect"); return 2; } while(1) { memset(buf, 0, sizeof(buf)); //从标准输入获取字符串 if (!gets(buf)) { perror("gets"); return 3; } if (strcmp(buf, "quit") == 0) break; if (write(client_fd, buf, strlen(buf)) != strlen(buf)) { perror("write"); return 4; } } close(client_fd); return 0; }
3、最后是一个简单的Makefile
all: gcc server.c -o server gcc client.c -o client clean: rm -rf server client
编译后程序就能跑起来了,使用netstat命令查看连接状态如下:
客户端退出后,服务端还是在监听新连接的到来。
探讨
1、对于服务端代码的accept函数一般情况我们是将第二、三个入参置为空的,表示我们不关注客户端信息,如下
accept_fd = accept(listen_fd, NULL, NULL);
此处我们为了打印客户端ip和端口,因此传入了我们定义的结构体,用于获取客户端信息。
2、关于服务端监听ip,或者是说绑定的ip
一般我们服务端程序监听全网段ip,即在调用bind函数时ip地址使用INADDR_ANY作为参数,如下,
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
这两种使用的区别我们可以通过netstat命令查看,对比如下,
上面的图为设置了具体监听ip的程序,下面是使用INADDR_ANY为参数的程序。由此,我们编程时就需要根据环境上是否有多个ip,程序是否需要监听所有网卡的端口来确定我们函数的参数设置。
3、客户端程序一般不调用bind函数
在客户端程序中我们一般不调用bind函数,因为我们其实一般并不关心客户端使用什么ip和什么端口和服务端通信,内核会替你选择一个未被占用的端口以及能和服务端通信的ip来发起连接。但是作为客户端,我们是否可以使用bind函数呢?答案是肯定的。考虑以下场景,如果我们想要指定客户端连接从哪个ip出去,使用哪个端口,这时候就必须使用bind函数了,这也就是bind函数的作用。
我们将上面客户端的程序稍微修改一下,增加bind操作。为了更好的看出bind的结果,我在环境上配了两个ip,让客户端和服务端各在一个ip上进行通信。我们配置客户端使用5555号端口,绑定在ip:192.168.0.107上,如代码所示:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <arpa/inet.h> #include <netinet/in.h> #define PORT 5000 #define IP "192.168.0.106" int main() { int client_fd; char buf[1024] = {0};//读写缓冲区 struct sockaddr_in client_addr; ssize_t size; //创建socket if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } /**************************************************************************/ //客户端使用bind函数绑定ip和端口 struct sockaddr_in client_bind; client_bind.sin_family = AF_INET; client_bind.sin_port = htons(5555); client_bind.sin_addr.s_addr = inet_addr("192.168.0.107"); if (bind(client_fd, (struct sockaddr*)&client_bind, sizeof(client_bind)) < 0) { perror("bind"); return 5; } /**************************************************************************/ //填充ip端口信息 client_addr.sin_family = AF_INET; client_addr.sin_port = htons(PORT); client_addr.sin_addr.s_addr = inet_addr(IP); //向服务端发起连接 if (connect(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) { perror("connect"); return 2; } while(1) { memset(buf, 0, sizeof(buf)); //从标准输入获取字符串 if (!gets(buf)) { perror("gets"); return 3; } if (strcmp(buf, "quit") == 0) break; if (write(client_fd, buf, strlen(buf)) != strlen(buf)) { perror("write"); return 4; } } close(client_fd); return 0; }
编译运行后同样使用netstat命令观察连接状态,如下:
对比之前的截图我们可以发现,客户端的ip和端口已经变成我们设置的值。所以说,bind在客户端也是可以使用的。
相关文章推荐
- linux下一个简单的TCP通信程序——客户端向服务端发送任意字符串
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- linux网络编程之用socket实现简单客户端和服务端的通信(基于TCP)
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- Linux基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- Linux基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- linux网络编程之用socket实现简单客户端和服务端的通信(基于TCP)
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- C++基于TCP/IP简单的客户端、服务器通信程序实例
- 一个简单的基于node.js的TCP服务器和基于C++的TCP客户端通信示例程序
- Java简单实现TCP服务端和客户端通信
- VC面向对象的方式 写一个基于TCP的 客户端服务端程序 (SOCKET)
- 基于TCP网络通信的自动升级程序源码分析-客户端请求服务器上的升级信息