赞
踩
ctontrol + C:强制结束进程
并不是所有的地址都是可以用的
1,网络地址是由4字节32位构成,
2,以0开头的地址,都是不可以用的
3,以0结尾的地址,表示网段,而不是具体地址
4,224到239开头的地址,是组播
地址,不可用于点对点的传输
5,240到255开头的地址,实验用的,保留,一般不做服务器或者终端地址
A(大型网络)、B(中型网络)、C(小型网络)类用于不同规模的网络
D 类用于组播(类似广播)
E 类保留,用于实验、私有网络
端口并非只有服务器才会使用,客户端也一样会使用。
公认端口(Well Known Ports):又称常用端口,从0到1024,紧密绑定于一些特定服务。通常这些端口的通信明确表明了某种服务的协议,这种端口是不可再重新定义它的作用对象。netstat -an
注册端口(Registered Ports):端口号从1025到49151,这些端口多数没有明确定义服务对象,不同程序可根据实际需求自定义,远程控制软件和木马程序都会由这些端口的定义。
动态和/或私有端口(Dynamic and/or Private Ports):端口号从49152到65535(不要轻易作为服务器的监听端口)。特别是一些木马程序非常喜欢用这些端口,这些端口常常不被注意,易隐蔽。
协议就是,一种网络交互中数据格式和交互流程的约定
。通过协议,我们可以与远程设备进行数据交互,请求服务或完成对方的服务。
48字节
文本流
GET请求,1.1版本,Host:向网站发送数据;UA:客户端浏览器信息;
Accept:接收信息,格式,语言类型
6字节,字节流
传输控制协议(TCP,Transmission Control Protocol),面向连接,可靠,基于字节流(以字节为单位) 的传输层通信协议。安全可靠但牺牲了性能。
交互流程:
套接字有很多种,用的最多的TCP和UDP的套接字。
#include <sys/socket.h>
/* socket创建
- domain:使用的协议族(Protocol Family)信息
- type:数据传输类型信息
- protocol:计算机之间通信种使用的协议信息
*/
int socket(int domain, int type, int protocol);
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
/* socket绑定
- sockfd:socket套接字文件描述符
- myaddr:结构体,存放地址信息的结构体变量地址值,IPv4,IPV6
- addrlen:
*/
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);//绑定成功返回0,失败返回-1
ntohs 可以理解为:把short型数据从网络字节序转化为主机字节序。
struct sockaddr_in addr;
char *serv_ip = "221.214.168.13";//声明IP地址字符串
char *serv_port = "9190";//声明端口号
memset(&add, e,sizeof(addr));//结构体变量addr的所有成员初始化为0
addr.sin_fimily = AF_INET;//指定地址族
/*
每次创建服务器端套接字都要输入IP地址,会很繁琐,此时可初始化地址信息为INADDRANY。
addr.sin_addr.s_addr = htonl(INADDRANY);
*/
add.sin_addr.s_addr = inet_addr(serv_ip);//基于字符串的IP地址初始化
add.sin_port=htons(atoi(serv_port));//基于字符串的端口号初始化
#include <sys/socket.h>
/*
- sock:套接字文件描述符
- backlog:连接请求等待队列的长度,若为5,则队列长度为5,最多使5个连接请求进入队列
*/
int listen(int sock, int backlog);//成功返回0,失败返回-1.
#include <sys/socket.h>
/*
- sock:套接字文件描述符
- addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后传递来的地址变量参数天才客户端地址信息
- addrlen: addr结构体参数的长度。
*/
int accept(int sock, struct sockaddr*addr, socklen_t *addrlen);//成功返回0,失败返回-1.
基于TCP/IP网络协议的套接字分为:TCP和UDP套接字。
void server_func()
{
printf("%s(%d):%s\n", __FILE__, __LINE__, __FUNCTION__);
int server_sock, client_sock;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t client_addr_size;
//此处省略了一些,socket的配置认证,eg:版本号等
server_sock = socket(PF_INET, SOCK_STREAM, 0);
if (server_sock < 0) {
//error_handling("create socket error!");
std::cout << "create socket error!\n";
return;
}
memset(&server_addr, 0 , sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("0.0.0.0");// htonl(INADDR_ANY); 0.0.0.0 表示监听全网段,包括内外网
server_addr.sin_port = htons(9444);
int ret = bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
//error_handling("bind socket error!");
std::cout << "server bind socket error!\n";
close(server_sock);
return;
};
client_addr_size = sizeof(client_addr);
ret = listen(server_sock, 3);
if (ret == -1) {
//error_handling("listen socket error!");
std::cout << "server listen socket error!\n";
close(server_sock);
return;
};
//回声服务器-原理:服务端循环接收客户端的消息
char buffer[1024];
while (1)
{
memset(buffer, 0, sizeof(buffer));
client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_size);
if (client_sock == -1) {
//error_handling("accept server socket error!");
std::cout << "server accept socket error!\n";
close(server_sock);
return;
}
ssize_t len = 0;
while ((len = read(client_sock, buffer, sizeof(buffer))) > 0)
{
len = write(client_sock, buffer, strlen(buffer));
if (len != (ssize_t)strlen(buffer)) {
//error_handling("write message failed!");
std::cout << "server write message failed!\n";
close(server_sock);
return;
}
std::cout << "server read & write success!, buffer:" << buffer <<"__len:"<< len << std::endl;
memset(buffer, 0, len);//清理
}
close(client_sock);//服务端关闭的时候,客户端会自动关闭
};
close(server_sock);
}
void client_func()
{
int client = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(9444);
int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (ret == -1) {
std::cout << "client connect failed!\n";
close(client);
return;
}
std::cout << "client connect server is success!\n";
char buffer[256] = "";
while (1)
{
fputs("Input message(Q to quit):", stdout);//提示语句,输入Q结束
fgets(buffer, sizeof(buffer), stdin);//对文件的标准输入流操作 读取buffer的256字节
if (strcmp(buffer, "q\n") == 0 || (strcmp(buffer, "Q\n") == 0)) {
break;
}
size_t len = strlen(buffer);
size_t send_len = 0;
//当数据量很大时,并不能一次把所有数据全部发送完,因此需要分包发送
while (send_len < len)
{
ssize_t ret = write(client, buffer + send_len, len - send_len);//send_len 记录分包的标记
if (ret <= 0) {//连接出了问题
fputs("may be connect newwork failed,make client write failed!\n", stdout);
close(client);
return;
}
send_len += (size_t)ret;
std::cout << "client write success, msg:" << buffer << std::endl;
}
memset(buffer, 0, sizeof(buffer));
//当数据量很大时,并不能一次把所有数据全部读取完,因此需要分包读取
size_t read_len = 0;
while (read_len < len)
{
size_t ret = read(client, buffer + read_len, len - read_len);
if (ret <= 0) {//连接出了问题
fputs("may be connect newwork failed, make client read failed!\n", stdout);
close(client);
return;
}
read_len += (size_t)ret;
}
std::cout << "from server:" << buffer << std::endl;
};
close(client);
std::cout << "client done!" << std::endl;
}
#include <sys/wait.h>
void server_client_connect()
{
//创建进程
pid_t pid = fork();
if (pid == 0) {//为0时表示在子进程
sleep(1);//为了让服务端先执行
client_func();
}
else if (pid > 0) {//主进程为服务端进程
server_func();
int status = 0;
wait(&status);
}
else
{
std::cout << "fork failed!" << pid << std::endl;
}
}
/*
* 云计算器-需求:
1.客户端连接到服务器端后以1字节整数形式传递待算数字个数。 (个数是1字节)
2.客户端向服务器端传递的每个整数型数据占用4字节。 (每个数是4字节)
3.传递整数型数据后接着传递运算符。运算符信息占用1字节。 (运算符信息1字节)
4.选择字符+、-、*之一传递。
5. 服务器端以4字节整数型向客户端传回运算结果。
6. 客户端得到运算结果后终止与服务器端的连接。
*/
我们知道,TCP套接字的数据收发无边界。服务器端即使调用1次write函数传输40字节的数据,客户端也有可能通过4次read函数调用每次读取10字节。但此处也有一些疑问,服务器端一次性传输了40字节,而客户端居然可以缓慢地分批接收。客户端接收10字节后,剩下的30字节在何处等候呢?是不是像飞机为等待着陆而在空中盘旋一样,剩下30字节也在网络中徘徊并等待接收呢?
实际上,write函数调用后并非立即传输数据,read函数调用后也并非马上接收数据。更准确地说,如下图所示,write函数调用瞬间,数据将移至输出缓冲;read函数调用瞬间,从输人缓冲读取数据。
调用write函数时,数据将移到输出缓冲,在适当的时候(不管是分别传送还是一次性传送)传向对方的输入缓冲。这时对方将调用read函数从输入缓冲读取数据。这些I/O 缓冲特性可整理如下。
A: I/O缓冲在每个TCP套接字中单独存在。
B: I/O缓冲在创建套接字时自动生成。
C: 即使关闭套接字也会继续传递输出缓冲中遗留的数据。
D: 关闭套接字将丢失输入缓冲中的数据。
那么,下面这种情况会引发什么事情?理解了I/O缓冲后,其流程∶
“客户端输入缓冲为50字节,而服务器端传输了100字节。”
这的确是个问题。输入缓冲只有50字节,却收到了100字节的数据。可以提出如下解决方案∶
填满输入缓冲前迅速调用read函数读取数据,这样会腾出一部分空间,问题就解决了。
其实根本不会发生这类问题,因为TCP会控制数据流。
TCP中有滑动窗口(Sliding Window)协议,用对话方式呈现如下。
套接字A∶"你好,最多可以向我传递50字节。"
套接字B∶"OK!"
套接字A∶"我腾出了20字节的空间,最多可以收70字节。
套接字B∶"OK!"
数据收发也是如此,因此TCP中不会因为缓冲溢出而丢失数据。
但是会因为缓冲而影响传输效率
(客户端主动发起 - 服务端应答)
首先,请求连接的主机A向主机B传递如下信息∶
[SYN] SEQ:1000, ACK: -
该消息中SEQ为1000,ACK为空,而SEQ为1000的含义如下∶
"现传递的数据包序号为1000,如果接收无误,请通知我向您传递1001号数据包。"这是首次请求连接时使用的消息,又称SYN。SYN是Synchronization的简写,表示收发数据前传输的同步消息。
接下来主机B向A传递如下消息∶
[SYN+ACK]SEQ:2000, ACK:1001
此时SEQ为2000,ACK为1001,而SEQ为2000的含义如下∶
“现传递的数据包序号为2000如果接收无误,请通知我向您传递2001号数据包。”
而ACK1001的含义如下∶
“刚才传输的SEQ为1000的数据包接收无误,现在请传递SEQ为1001的数据包。”
对主机A首次传输的数据包的确认消息(ACK1001)和为主机B传输数据做准备的同步消息(SEQ2000)拥绑发送,因此,此种类型的消息又称SYN+ACK。
收发数据前向数据包分配序号,并向对方通报此序号,这都是为防止数据丢失所做的准备。通过向数据包分配序号并确认,可以在数据丢失时马上查看并重传丢失的数据包。因此,TCP可以保证可靠的数据传输。最后观察主机A向主机B传输的消息∶
[ACK]SEQ:1001, ACK:2001
TCP连接过程中发送数据包时需分配序号。
在之前的序号1000的基础上加1,也就是分配1001。此时该数据包传递如下消息∶
“已正确收到传输的SEQ为2000的数据包,现在可以传输SEQ为2001的数据包。”
这样就传输了添加ACK2001的ACK消息。至此,主机A和主机B确认了彼此均就绪。
利用TCP三次握手的攻击:
客户端不断的只做第一次握手,这样服务端一直在根据这次握手不断的创建连接分配序号,但这些创建的连接需要两个小时左右的超时时间,,这样就比较占用服务端资源,数据量过大阻塞服务端。
UDP是DDOS攻击(网络分布式拒绝服务攻击)的最主要形式,可以伪造发件人地址,数据无法拒收,需要数据包接收,但由于伪造了发件人地址,拒收又找不到发件人
我们可以通过信件说明UDP的工作原理,这是讲解UDP时使用的传统示例,它与UDP特性完全相符。寄信前应先在信封上填好寄信人和收信人的地址,之后贴上邮票放进邮筒即可。当然,信件的特点使我们无法确认对方是否收到。另外,邮寄过程中也可能发生信件丢失的情况。也就是说,信件是一种不可靠的传输方式。与之类似,UDP提供的同样是不可靠的数据传输服务。
“既然如此,TCP应该是更优质的协议吧?”
如果只考虑可靠性,TCP的确比UDP好。但UDP在结构上比TCP更简洁。UDP不会发送类似ACK的应答消息,也不会像SEQ那样给数据包分配序号。因此,UDP的性能有时比TCP高出很多。编程中实现UDP也比TCP简单。另外,UDP的可靠性虽比不上TCP,但也不会像想象中那么频繁地发生数据损毁。因此,在更重视性能而非可靠性的情况下,UDP是一种很好的选择。
既然如此,UDP的作用到底是什么呢?为了提供可靠的数据传输服务,TCP在不可靠的IP层进行流控制,而UDP就缺少这种流控制机制。
流控制是区分UDP和TCP的最重要的标志。但若从TCP中除去流控制,所剩内容也屈指可数。也就是说,TCP的生命在于流控制。
如果把TCP比喻为电话,把 UDP比喻为信件。但这只是形容协议工作方式,并没有包含数据交换速率。请不要误认为"电话的速度比信件快,因此TCP 的数据收发速率也比 UDP快"。实际上正好相反。TCP的速度无法超过UDP,但在收发某些类型的数据时有可能接近 UDP。例如,每次交换的数据量越大,TCP的传输速率就越接近 UDP的传输速率。
TCP与UDP结合使用:用TCP做流控制,用UDP做数据传输。
UDP包数据传输的无序性,由于路由器的状态变化,可能先发的包后到
UDP与TCP区别:
UDP 中的服务器端和客户端没有连接
UDP服务器端/客户端不像TCP那样在连接状态下交换数据,因此与TCP不同,无需经过连接过程。也就是说,不必调用TCP连接过程中调用的listen函数和accept函数。UDP中只有创建套接字的过程和数据交换过程。
UDP服务器端和客户端均只需 1个套接字
TCP中,套接字之间应该是一对一的关系。若要向10个客户端提供服务,则除了守门的服务器套接字外,还需要10个服务器端套接字。但在UDP中,不管是服务器端还是客户端都只需要1 个套接字。之前解释UDP原理时举了信件的例子,收发信件时使用的邮筒可以比喻为UDP套接字。只要附近有1个邮筒,就可以通过它向任意地址寄出信件。同样,只需1个UDP套接字就可以向任意主机传输数据
#include<sys/socket.h>
→成功时返回传输的字节数,失败时返回-1。
ssize_t sendto(int sock,void*buff,size_t nbytes,int flags,struct sockaddr *to, socklen_t addrlen);
● sock
用于传输数据的UDP套接字文件描述符。
● buff
保存待传输数据的缓冲地址值。
● nbytes
待传输的数据长度,以字节为单位。
● flags
可选项参数,若没有则传递0。
● to
存有目标地址信息的sockaddr结构体变量的地址值。
● addrlen
传递给参数to的地址值结构体变量长度。
sendto 函数与之前的TCP输出函数最大的区别在于,此函数需要向它传递目标地址信息。接下来介绍接收UDP数据的函数。UDP数据的发送端并不固定,因此该函数定义为可接收发送端信息的形式,也就是将同时返回UDP数据包中的发送端信息。
#include<sys/socket.h>
//→成功时返回接收的字节数,失败时返回-1。
ssize_t recvfrom(int sock, void *buff,size_t nbytes, int flags,
struct sockaddr*from, socklen_t*addrlen);
●sock用于接收数据的UDP套接字文件描述符。
●buff保存接收数据的缓存地址值
●nbytes 可接收的最大字节数,故无法超过参数buf所指的缓冲大小。
●flags可选项参数,若没有则传入0。
●from存有发送端地址信息的sockaddr结构体变量的地址值。
●addrlen 保存参数from的结构体变量长度的变量地址值。
编写UDP程序时最核心的部分就在于上述两个函数,这也说明二者在UDP数据传输中的地位。
//UDP回声服务器
#define BUF_SIZE 30 //buffer字节大小
//需要控制台 输入端口
void udp_server(int argc, char* argv[])
{
if (argc != 2) {//校验端口参数,
printf("Usage : %s <port>\n", argv[0]);
error_handling("argement is error: 端口");
}
int serv_sock;
char message[BUF_SIZE];
socklen_t clnt_adr_sz;
struct sockaddr_in serv_adr, clnt_adr;
//创建socket
serv_sock = socket(PF_INET, SOCK_DGRAM, 0);//UDP的socket SOCK_DGRAM: 报文类型
if (serv_sock == -1)
error_handling("UDP socket creation error");
memset(&serv_adr, 0, sizeof(serv_adr));
//配置socket的地址族
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY->0.0.0.0 htonl:主机字节序 转网络字节序 l:long型 4或8字节
serv_adr.sin_port = htons((uint16_t)atoi(argv[1]));//需要输入端口 htons:主机字节序 转网络字节序 s:short型,2字节
//绑定
if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
error_handling("bind() error");
//收发数据
ssize_t str_len;
while (1)
{
clnt_adr_sz = sizeof(clnt_adr);
str_len = recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);//利用分配的地址接收数据。不限制数据传输对象
sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz);//函数调用同时获取数据传输端的地址。正是利用该地址将接收的数据逆向重传。
}
close(serv_sock);
}
void udp_client(int argc, char* argv[])
{
if (argc != 3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
error_handling("argement is error: IP");
}
int sock;
char message[BUF_SIZE];
socklen_t adr_sz;
struct sockaddr_in serv_adr, from_adr;
//创建socket
sock = socket(PF_INET, SOCK_DGRAM, 0);
if (sock == -1)
error_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));//清理,防止默认值
//配置socket的地址族
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]);//控制台输入ip地址,eg:127.0.0.1
serv_adr.sin_port = htons((uint16_t)atoi(argv[2]));//控制台输入端口
//收发数据
ssize_t str_len;
while (1)
{
fputs("Insert message(q to quit): ", stdout);
fgets(message, sizeof(message), stdin);
if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))//字符串比较
break;
ssize_t len = sendto(sock, message, strlen(message), 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
memset(message, 0, len);
adr_sz = sizeof(from_adr);
str_len = recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_adr, &adr_sz);
printf("Message from server: %s", message);
message[str_len] = 0;
}
close(sock);
}
//主进程开启客户端,再开启服务端,是为了在主进程调试方便
void udp_server_client_connect(int argc, char* argv[])
{
char* argv0 = "";
pid_t pid = fork();
if (pid < 0)
std::cout << "fork failed!" << pid << std::endl;
if (pid == 0)//开启新进程,子进程(服务端进程)
{
int argc = 2;
char* argv[] = { argv0, (char*)"9555" };
udp_server(argc, argv);
}
else//pid > 0 , 父进程(客户端进程)
{
int argc = 3;
char* argv[] = { argv0, (char*)"127.0.0.1", (char*)"9555" };
udp_client(argc, argv);
int status = 0;
wait(&status);
}
}
综上所述,调用sendto函数时自动分配IP和端口号,因此,UDP客户端中通常无需额外的地址分配过程。
我们几乎可以针对上表中的所有可选项进行读取(Get)和设置(Set)(当然,有些可选项只能进行一种操作)。可选项的读取和设置通过如下2个函数完成。
#include<sys/socket.h>
//→成功时返回0,失败时返回-1。
int getsockopt(int sock, int level,int optname, void *optval, socklen_t *optlen);
●sock :用于查看选项套接字文件描述符。
●level要查看的可选项的协议层。
●optname 要查看的可选项名。
●optval保存查看结果的缓冲地址值。
●optlen向第四个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数。
#include<sys/socket.h>
//→成功时返回0,失败时返回-1。
int setsockopt(int sock, int level, int optname, const void*optval, socklen_t optlen);
●sock用于更改可选项的套接字文件描述符。
●level要更改的可选项协议层。
●optname 要更改的可选项名。
●optval 保存要更改的选项信息的缓冲地址值。
●optlen 向第四个参数optval传递的可选项信息的字节数。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
int snd_buf=1024*3, rcv_buf=1024*3;
int state;
socklen_t len;
sock=socket(PF_INET, SOCK_STREAM, 0);
state=setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
if(state)
error_handling("setsockopt() error!");
state=setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
if(state)
error_handling("setsockopt() error!");
len=sizeof(snd_buf);
state=getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
if(state)
error_handling("getsockopt() error!");
len=sizeof(rcv_buf);
state=getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
if(state)
error_handling("getsockopt() error!");
printf("Input buffer size: %d \n", rcv_buf);
printf("Output buffer size: %d \n", snd_buf);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
服务器由于迟迟得不到响应,出现Time-wait等待状态,
//解决Time-wait的问题
getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
printf("SO_REUSEADDR = %d\n", optval);
//设置optval
if (optval == 0)
optval = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, addrlen);
//------------------------ Time-wait--------------------------
#define TRUE 1
#define FALSE 0
void tw_tcp_server()
{
int sock, client, optval = 0;
struct sockaddr_in addr, cliAddr;
socklen_t addrlen = sizeof(addr);
char buffer[256] = "";
sock = socket(PF_INET, SOCK_STREAM, 0);
//解决Time-wait的问题
getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
printf("SO_REUSEADDR = %d\n", optval);
//设置optval
if (optval == 0)
optval = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, addrlen);
//查看新的optval
getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, &addrlen);
printf("SO_REUSEADDR = %d\n", optval);
memset(&addr, 0, addrlen);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(9526);
addrlen = sizeof(addr);
if (bind(sock, (struct sockaddr*) & addr, addrlen) == -1)
{
error_handling("tw_tcp_server bind failed");
}
listen(sock, 3);
client = accept(sock, (struct sockaddr*)&cliAddr, &addrlen);
read(client, buffer, sizeof(buffer));
close(client);
close(sock);
}
void tw_tcp_client()
{
int client = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(9526);
int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (ret == -1) {
std::cout << "client connect failed!\n";
close(client);
return;
}
std::cout << "client connect server is success!\n";
char buffer[256] = "";
while (ret == 0)
{
fputs("Input message(Q to quit):", stdout);//提示语句,输入Q结束
fgets(buffer, sizeof(buffer), stdin);//对文件的标准输入流操作 读取buffer的256字节
if (strcmp(buffer, "q\n") == 0 || (strcmp(buffer, "Q\n") == 0)) {
break;
}
size_t len = strlen(buffer);
size_t send_len = 0;
//当数据量很大时,并不能一次把所有数据全部发送完,因此需要分包发送
while (send_len < len)
{
ssize_t ret = write(client, buffer + send_len, len - send_len);//send_len 记录分包的标记
if (ret <= 0) {//连接出了问题
fputs("may be connect newwork failed,make client write failed!\n", stdout);
close(client);
return;
}
send_len += (size_t)ret;
std::cout << "client write success, msg:" << buffer << std::endl;
}
memset(buffer, 0, sizeof(buffer));
//当数据量很大时,并不能一次把所有数据全部读取完,因此需要分包读取
size_t read_len = 0;
while (read_len < len)
{
size_t ret = read(client, buffer + read_len, len - read_len);
if (ret <= 0) {//连接出了问题
fputs("may be connect newwork failed, make client read failed!\n", stdout);
close(client);
return;
}
read_len += (size_t)ret;
}
std::cout << "from server:" << buffer << std::endl;
};
close(client);
std::cout << "client done!" << std::endl;
}
void tw_func(char* option)
{
if (strcmp(option, "1") == 0)
{
tw_tcp_server();
tw_tcp_server();
}
else {
tw_tcp_client();
}
}
tw_func(argv[1]);//Time-wait超时等待
NODELAY:没有延时。
Nagle算法是以他的发明人John Nagle的名字命名的,它用于自动连接许多的小缓冲器消息;这一过程(称为nagling)通过减少必须发送包的个数来增加网络软件系统的效率。
从上图中可以得到如下结论:
int opt_val=1;
setsockopt(sock, IPPROTO_TCP,TCP_NODELAY,(void*)&opt_val, sizeof(opt_val));
int opt_val;socklen_t opt_len;
opt_len=sizeof(opt_val);
getsockopt(sock,IPPROTO_TCP,TCP_NODELAY,(void*)&opt_val,&opt_len);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。