UNIX网络编程——TCP回射服务器/客户端程序
下面通过最简单的客户端/服务器程序的实例来学习socket API。
serv.c 程序的功能是从客户端读取字符然后直接回射回去:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h> #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0) int main(void)
{
int listenfd; //被动套接字(文件描述符),即只可以accept
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
// listenfd = socket(AF_INET, SOCK_STREAM, 0)
ERR_EXIT("socket error"); struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
/* inet_aton("127.0.0.1", &servaddr.sin_addr); */ int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt error"); if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind error"); if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
ERR_EXIT("listen error"); struct sockaddr_in peeraddr; //传出参数
socklen_t peerlen = sizeof(peeraddr); //传入传出参数,必须有初始值
int conn; // 已连接套接字(变为主动套接字,即可以主动connect)
if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
ERR_EXIT("accept error");
printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
struct sockaddr_in localaddr;
char serv_ip[20];
socklen_t local_len = sizeof(localaddr);
memset(&localaddr, 0, sizeof(localaddr));
if( getsockname(conn,(struct sockaddr *)&localaddr,&local_len) != 0 )
ERR_EXIT("getsockname error");
inet_ntop(AF_INET, &localaddr.sin_addr, serv_ip, sizeof(serv_ip));
printf("host %s:%d\n", serv_ip, ntohs(localaddr.sin_port)); char recvbuf[1024];
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
fputs(recvbuf, stdout);
write(conn, recvbuf, ret);
} close(conn);
close(listenfd); return 0;
}
cli.c 的作用是从标准输入得到一行字符,然后发送给服务器后从服务器接收,再打印在标准输出:
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h> #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0) int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
// listenfd = socket(AF_INET, SOCK_STREAM, 0)
ERR_EXIT("socket error"); struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
/* inet_aton("127.0.0.1", &servaddr.sin_addr); */ if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect error");
struct sockaddr_in localaddr;
char cli_ip[20];
socklen_t local_len = sizeof(localaddr);
memset(&localaddr, 0, sizeof(localaddr));
if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )
ERR_EXIT("getsockname error");
inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));
printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port)); char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{ write(sock, sendbuf, strlen(sendbuf));
read(sock, recvbuf, sizeof(recvbuf)); fputs(recvbuf, stdout); memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
} close(sock); return 0;
}
由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。注意,客户端不是不允许调用bind(),只是没有必要调用bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
先编译运行服务器:
huangcheng@ubuntu:~$./serv
然后在另一个终端里用netstat命令查看:
huangcheng@ubuntu:~$ netstat -anp | grep 5188
(并非所有进程都能被检测到,所有非本用户的进程信息将不会显示,如果想看到所有信息,则必须切换到 root 用户)
tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN 2998/serv
可以看到server程序监听5188端口,IP地址还没确定下来。现在编译运行客户端:
huangcheng@ubuntu:~$ ./cli
回到server所在的终端,看看server的输出:
huangcheng@ubuntu:~$ ./serv
recv connect ip=127.0.0.1 port=42107
可见客户端的端口号是自动分配的。再次netstat 一下:
huangcheng@ubuntu:~$ netstat -anp | grep 5188
(并非所有进程都能被检测到,所有非本用户的进程信息将不会显示,如果想看到所有信息,则必须切换到 root 用户)
tcp 0 0 0.0.0.0:5188 0.0.0.0:* LISTEN 2998/serv
tcp 0 0 127.0.0.1:5188 127.0.0.1:42107 ESTABLISHED 2998/serv
tcp 0 0 127.0.0.1:42107 127.0.0.1:5188 ESTABLISHED 3198/cli
应用程序中的一个socket文件描述符对应一个socket pair,也就是源地址:源端口号和目的地址:目的端口号,也对应一个TCP连接。
上面第一行即serv.c 中的listenfd;第二行即serv.c 中的sock; 第三行即cli 中的conn。2998和3198分别是进程id。
现在来做个测试,先serv.c中的把33~35行的代码注释掉。
首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server,结果是:
huangcheng@ubuntu:~$ ./serv
bind error: Address already in use
这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口。我们用netstat命令查看一下:
huangcheng@ubuntu:~$ netstat -anp | grep 5188
(并非所有进程都能被检测到,所有非本用户的进程信息将不会显示,如果想看到所有信息,则必须切换 到 root 用户)
tcp 0 0 127.0.0.1:5188 127.0.0.1:42108 FIN_WAIT2 -
tcp 1 0 127.0.0.1:42108 127.0.0.1:5188 CLOSE_WAIT 3260/cli
server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态。
现在用Ctrl-C把client也终止掉,再观察现象:
huangcheng@ubuntu:~$ netstat -anp | grep 5188
(并非所有进程都能被检测到,所有非本用户的进程信息将不会显示,如果想看到所有信息,则必须切换到 root 用户)
tcp 0 0 127.0.0.1:5188 127.0.0.1:42108 TIME_WAIT -
huangcheng@ubuntu:~$ ./serv
bind error: Address already in use
client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximumsegment lifetime)的时间后才能回到CLOSED状态,需要有MSL 时间的主要原因是在这段时间内如果最后一个ack段没有发送给对方,则可以重新发送。因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后就可以再次启动server了。至于为什么要规定TIME_WAIT的时间请大家参考UNP 2.7节。
在server的TCP连接没有完全断开之前不允许重新监听是不合理的,因为,TCP连接没有完全断开指的是connfd(127.0.0.1:5188)没有完全断开,而我们重新监听的是listenfd(0.0.0.0:5188),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。将原来注释的33~35行代码打开,问题解决。
先运行服务器,在运行客户端:
huangcheng@ubuntu:~$ ./serv
recv connect ip=127.0.0.1 port=42107
host 127.0.0.1:5188
huangcheng
ctt
huangcheng@ubuntu:~$ ./cli
host 127.0.0.1:42107
huangcheng
huangcheng
ctt
ctt
UNIX网络编程——TCP回射服务器/客户端程序的更多相关文章
- UNIX网络编程——UDP回射服务器程序(初级版本)以及漏洞分析
该函数提供的是一个迭代服务器,而不是像TCP服务器那样可以提供一个并发服务器.其中没有对fork的调用,因此单个服务器进程就得处理所有客户.一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是 ...
- UNIX网络编程---TCP客户/服务器程序示例(五)
一.概述 客户从标准输入读入一行文本,并写给服务器 服务器从网络输入读入这行文本,并回射给客户 客户从网络输入读入这行回射文本,并显示在标准输出上 二.TCP回射服务器程序:main函数 这里给了函数 ...
- TCP回射服务器修订版(ubuntu 18.04)
一.需求 把https://www.cnblogs.com/soldierback/p/10673345.html中的TCP回射服务器程序重写成使用select来处理任意个客户的单进程 程序,而不是为 ...
- 服务器编程入门(10)TCP回射服务器实现 - 并发
问题聚焦: 在前面我们大概浏览了一下服务器编程需要掌握的一些知识和技术,以及架构思想. 实践,才是检验真理的唯一标准..从这节起我们将在这些技术的基础上,一步步实现以及完善一个服 ...
- UNIX网络编程——使用select函数编写客户端和服务器
首先看原先<UNIX网络编程--并发服务器(TCP)>的代码,服务器代码serv.c: #include<stdio.h> #include<sys/types.h> ...
- TCP回射服务器程序:main函数
TCP回射并发服务器 1.创建套接字,绑定服务器的众所周知端口 创建一个TCP套接字,在待绑定到该TCP套接字的网际网套接字地址结构中填入通配地址(INADDR_ANY) 和服务器的众所知周(SERV ...
- UNIX网络编程——TCP服务器“拒绝服务攻击” 解决方案
前面的博客<<使用select和shutdown>>里面的拒绝服务型攻击也有提到. 说这是一个完全的解决方案,其实有点夸大了,但这个方案确实可以缓解TCP服务器遭受" ...
- 第5章-unix网络编程 TCP/服务端程序示例
这一章主要是完成一个完整的tcp客户/服务器程序.通过一很简单的例子.弄清客户和服务器如何启动,如何终止,发生了某些错误会发生什么.这些事很重要的 客户端代码 #include "unp. ...
- UNIX网络编程——tcp流协议产生的粘包问题和解决方案
我们在前面曾经说过,发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体 ...
随机推荐
- 关于导入excel问题
关于导入excel问题: 在VS中可以导入,部署在IIS上无法导入的原因: 1.可能部署IIS上发布的文件设置为只读,没有写入权限.解决方法为:设置程序发布的文件夹,添加写入权限,以及asp.net ...
- Intellij idea: java.lang.ClassNotFoundException:javax.el.ELResolver异常解决办法
使用Intellij idea编译过程中遇到的问题及解决办法. 由于编译时候报javax.servlet不存在,我把tomcat下的servlet-api.jar放到了External Librari ...
- C++多态实现原理
C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数.如果对象类型是派生类,就调用派生类的函数:如果对象类型是基类 ...
- TensorFlow Serving-TensorFlow 服务
TensorFlow服务是一个用于服务机器学习模型的开源软件库.它处理机器学习的推断方面,在培训和管理他们的生命周期后采取模型,通过高性能,引用计数的查找表为客户端提供版本化访问. 可以同时提供多个模 ...
- Python学习--课本程序练习(周更)
1.绘制正方形螺旋线 import turtle turtle.setup(600,300,200,200) turtle.pensize(1) turtle.color('green') i=0 w ...
- MySQL数据库常用操作入门
一.MySQL MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle旗下产品.在WEB应用方面,MySQL是最好的RDBMS应用软件.MySQL体积小.速度快.总 ...
- 转:禁止360随身wifi驱动自动安装
from:http://bbs.360safe.com/thread-2643500-1-1.html 有曾经数次卸载随身wifi后,插上又会自动安装随身wifi驱动的,也可以看看下面的解决办法. 卸 ...
- 牛客网编程练习之PAT乙级(Basic Level):1034 写出这个数
AC代码: import java.util.*; /** * @author CC11001100 */ public class Main { public static void main(St ...
- Dynamics CRM2016 Set Values of all Data Types using Web API
之前的博客里有谈到了web api的增删改查,里面会涉及到各种类型字段的赋值,因为时间和精力关系,没有对所有的字段类型一一测试,这篇博文中给出了全部的 http://inogic.com/blog/2 ...
- Lua语言模型 与 Redis应用
Lua语言模型 与 Redis应用 标签: Java与NoSQL 从 2.6版本 起, Redis 开始支持 Lua 脚本 让开发者自己扩展 Redis. 本篇博客主要介绍了 Lua 语言不一样的设计 ...