Posix,意为可移植操作系统接口,它定义了操作系统应该为应用程序提供的接口标准。
Posix标准旨在期望获得源代码级别的软件可移植性。比如:在linux下写的程序,预期在Windows下也能正常运行。
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
if (argc != 2)
{
printf("Using:./server port\nExample:./server 5005\n\n"); return -1;
}
// 第1步:创建服务端的socket。
int listenfd;
if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
return -1;
}
// 第2步:把服务端用于通信的地址和端口绑定到socket上。
struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议族,在socket编程中只能是AF_INET。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
//servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
servaddr.sin_port = htons(atoi(argv[1])); // 指定通信端口。
if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )
{
perror("bind");
close(listenfd);
return -1;
}
// 第3步:把socket设置为监听模式。
if (listen(listenfd,5) != 0 )
{
perror("listen");
close(listenfd);
return -1;
}
// 第4步:接受客户端的连接。
int clientfd; // 连上来的客户端socket。
int socklen = sizeof(struct sockaddr_in); // struct sockaddr_in的大小
struct sockaddr_in clientaddr; // 客户端的地址信息。
clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, (socklen_t*)&socklen);
printf("client (%s) connect server success。。。\n", inet_ntoa(clientaddr.sin_addr));
// 第5步:与客户端通信,接收客户端发过来的报文后,将该报文原封不动返回给客户端。
char buffer[1024];
// memset(buffer, 0, 1024);
while (1)
{
int ret;
memset(buffer, 0, sizeof(buffer));
// 接收客户端的请求报文。
if ( (ret = recv(clientfd, buffer, sizeof(buffer), 0)) <= 0)
{
printf("ret = %d , client disconected!!!\n", ret);
break;
}
printf("recv msg: %s\n", buffer);
// 向客户端发送响应结果。
if ( (ret = send(clientfd, buffer, strlen(buffer), 0)) <= 0)
{
perror("send");
break;
}
printf("response client: %s success...\n", buffer);
}
// 第6步:关闭socket,释放资源。
close(listenfd);
close(clientfd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
if (argc != 3)
{
printf("Using:./client ip port\nExample:./client 127.0.0.1 5005\n\n"); return -1;
}
// 第1步:创建客户端的socket。
int sockfd;
if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
return -1;
}
// 第2步:向服务器发起连接请求。
struct hostent* h;
if ( (h = gethostbyname(argv[1])) == 0 ) // 指定服务端的ip地址。
{ printf("gethostbyname failed.\n"); close(sockfd); return -1; }
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通信端口。
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
// 向服务端发起连接清求。
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
{
perror("connect");
close(sockfd);
return -1;
}
char buffer[1024];
// 第3步:与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
for (int i = 0; i < 3; i++)
{
int ret;
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "这是第[%d]条消息!", i+1);
if ( (ret = send(sockfd, buffer, strlen(buffer),0)) <= 0) // 向服务端发送请求报文。
{
perror("send");
break;
}
printf("发送:%s\n", buffer);
memset(buffer,0,sizeof(buffer));
if ( (ret = recv(sockfd, buffer, sizeof(buffer), 0)) <= 0) // 接收服务端的回应报文。
{
printf("ret = %d error\n", ret);
break;
}
printf("从服务端接收:%s\n", buffer);
sleep(1);
}
// 第4步:关闭socket,释放资源。
close(sockfd);
}
int socket(int domain, int type, int protocol);
调用socket()函数会创建一个套接字(socket)对象。套接字由两部分组成,文件描述符(fd)和 TCP控制块(Tcp Control Block,tcb) 。Tcb主要包括关系信息有网络的五元组(remote IP,remote Port, local IP, local Port, protocol),一个五元组就可以确定一个具体的网络连接。
listen(int listenfd, backlog);
服务端在调用listen()后,就开始监听网络上连接请求。第二个参数 backlog, 在Linux是指全连接队列的长度,即一次最多能保存 backlog 个连接请求。
客户端调用connect()函数,向指定服务端发起连接请求。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept()函数只做两件事,将连接请求从全连接队列中取出,给该连接分配一个fd并返回。
三次握手与listen/connect/accept三个函数有关,这里放到一起进行描述。
为什么要三次握手?
答:因为一个完整的TCP连接需要双方都得到确认,客户端发送请求和收到确认需要两次;服务端发送请求和收到确认需要两次,当中服务回复确认和发送请求合并为一次总共需要3次;才能保证双向通道是通的。
一个服务器的端口数是65535,为何能做到一百万的连接?
答:主要是因为一条连接是由五元组所组成,所以一个服务器的连接数是五个成员数的乘积。
如何应对Dos(Deny of Service,拒绝服务)攻击
答:Dos攻击就是利用三次握手的原理,模拟客户端只向服务器发送syn包,然后耗尽被攻击对象的资源。比较多的做法是利用防火墙,做一些过滤规则
至此,客户端与服务端已经成功建立连接,就可以相互通信了。
send/recv 函数主要负责数据的收发。
如何解决Tcp的粘包问题?
答:(1) 在包头上添加一个数据包长度的字段,用于数据的划分,实际项目中这个也用的最多;(2)包尾部加固定分隔符;
Tcp如何保证顺序到达?
答:顺序到达是由于TCP的延迟ACK的机制来保证的,TCP接收到数据并不是立即回复而是经过一个延迟时间,回复接收到连续包的最大序列号加1。如果丢包之后的包都需要重传。在弱网情况下这里就会有实时性问题和带宽占用的问题;
在服务器与客户端建立连接之后,会进行一些读写操作,完成读写操作后我们需要关闭相应的socket,好比操作完打开的文件要调用fclose关闭打开的文件一样。close过程涉及到四次挥手的全过程
注意:close操作只是让相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
文章参考于<零声教育>的C/C++linux服务期高级架构