TCP 套接字编程
API介绍
include <sys/socket.h>
int socket(int family,int type,int protocol)
@family:协议族,为0的时候系统根据后两个参数选择协议. 常用AF_INET
@type: 套接字类型,这边区分套接字是TCP还是UDP .如果是TCP就用SOCK_STREAM, 数据报套接字SOCK_DGRAM是UDP使用
@protocol: IPPROTO_TCP/IPROTO_UDP/IPROTO_SCTP 分别对应三种传输协议.如果是TCP直接写0也可以
返回值: 成功的时候返回非负的套接字.
int connect(int sockfd,const struct sockaddr * servaddr,socklen_t addrlen)
//相应结构体定义
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
struct sockaddr_in
{
short sin_family; //一般来说,AF_INET(地址族)PF_INET(协议族)
unsigned short sin_port; /* Portnumber (必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
struct in_addr sin_addr; /* Internet address. */
unsigned char sin_zero[8];
/* 字符数组sin_zero[8]的存在是为了保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等 */
};
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
//成功返回0 出错返回-1
//绑定端口和IP
int listen(int sockfd,int backlog);
int accept(int sockfd,struct sockaddr * cliaddr,socklen_t *addrlen);
//连接
关于listen 的作用
当我们调用socket()创建一个套接字时,这个套接字是一个主动连接的套接字,通过listen()将这个套接字转换为被动连接的,指定内核应该接受指向该套接字的连接请求。
这时候socket的状态从closed转换到listen状态
第二个参数指定了内核应该为相应套接字排队的最大连接数。内核会为每一个给定的监听套接字维护两个队列:完成连接队列和未完成连接队列。
完成连接队列: 在这个队列中的套接字状态都是ESTABLISHED
未完成连接队列: 众所周知,三次握手最后一次的的包是从客户端发出的,服务器端的套接字需处SYN_RCVD状态等待响应的ACK包发送过来,这个队列存放的就是这样的套接字
SYN泛滥
通过高速率向服务端发送SYN包来将这个未完成连接队列填满,同时在每个SYN的源地址IP都设置 成未知数,这样服务端的下一个ACK包就不知道发往什么地方,进而导致整个队列为满,使得合法的SYN排不上队,导致合法客户的连接请求被拒绝.
问题
服务器简单实现了并发,同时也解决僵尸进程的问题,但是这个服务器有一个问题,当客户端和服务器建立连接之后,我们通过kill杀死服务器中客户端对应的子进程时,客户端此时正阻塞在输入中,当我们再次输入文本的时候他将返回terminated prematurely .
这是一个错误信息,因为此时客户端正在应对两个描述符:套接字和文本输入.此时阻塞在文本输入中,但是正常来说当服务器发送FIN时我们总是想要客户端能够正确马上响应,所以程序应该阻塞在其中任何一个源的输入上.这也是select和poll两个函数的目的.
代码
server
/*************************************************************************
> File Name: main.cpp
> Author:
> Mail:
> Created Time: 2020年01月26日 星期日 16时39分52秒
************************************************************************/
# include <signal.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <sys/socket.h>
# include <unistd.h>
# include <string.h>
# include <stdlib.h>
# include <ctype.h>
# include <errno.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/ioctl.h>
# include <stdarg.h>
# include <fcntl.h>
# include<iostream>
using namespace std;
typedef void Sigfunc(int);
void sig_chld(int signo)
{
pid_t pid;
int stat;
if((pid=waitpid(-1,&stat,WNOHANG))>0)
cout<<"child terminated\n"<<endl;
return ;
}
Sigfunc*
signal_bind(int signo,Sigfunc* func)
{
struct sigaction act,oact;
act.sa_handler=func;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
act.sa_flags|=SA_RESTART;
if(sigaction(signo,&act,&oact)<=0)
return SIG_ERR;
return oact.sa_handler;
}
void
str_echo(int fd)
{
size_t n;
char buf[4096];
cout<<"str_echo"<<endl;
while((n=read(fd,buf,4096))>0)
{
write(fd,buf,strlen(buf));
memset(buf,0,sizeof(char)*4096);
}
if(n<=0)
cout<<"str_echo:read error"<<endl;
}
int
main(int argc,char ** argv)
{
int listenfd,connfd;
pid_t childpid;
socklen_t cliadr_len;
struct sockaddr_in cliaddr,servaddr;
signal_bind(SIGCHLD,sig_chld);
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
cout<<"socket error"<<endl;
exit(0);
}
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(8888);
if(bind(listenfd,(sockaddr *)&servaddr,sizeof(cliaddr))<0)
{
cout<<"bind error"<<endl;
exit(0);
}
if(listen(listenfd,20)<0){
cout<<"listen error"<<endl;
exit(0);
}
for(;;)
{
cliadr_len=sizeof(cliaddr);
connfd=accept(listenfd,(sockaddr*)&cliaddr,&cliadr_len);
if(errno==EINTR)
continue;
if(connfd<0)
exit(0);
if((childpid=fork())==0)
{
//child
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
}
client
/*************************************************************************
> File Name: main.cpp
> Author:
> Mail:
> Created Time: 2020年01月26日 星期日 16时39分52秒
************************************************************************/
# include <sys/types.h>
# include <sys/socket.h>
# include <netdb.h>
# include <unistd.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <ctype.h>
# include <errno.h>
# include <malloc.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/ioctl.h>
# include <stdarg.h>
# include <fcntl.h>
# include <fcntl.h>
# include<iostream>
using namespace std;
void
str_cin(int sockfd)
{
char sendline[4096],recvline[4096];
while(1)
{
cin>>sendline;
write(sockfd,sendline,strlen(sendline));
if(read(sockfd,recvline,4096)<=0)
{
cout<<"str_cli:server terminated permaturely";
exit(0);
}
cout<<recvline<<endl;
memset(recvline,0,sizeof(char)*4096);
}
}
int
main(int argc,char ** argv)
{
int sockfd;
struct sockaddr_in servaddr;
if(argc<2)
{
cout<<"usage:tcpcli <IPaddress>"<<endl;
exit(0);
}
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(8888);
inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
connect(sockfd,(struct sockaddr * )&servaddr,sizeof(servaddr));
str_cin(sockfd);
return 0;
}