一、read()、write()和recv()、send()
1.recv()和read()区别
功能相同,都能从套接口读数据到buf,但recv()只能用于套接口IO,read()可用于任何IO,读之后将数据从缓冲区请除,recv()多一个flag选项,指定接收行为:MSG_OOB 接收紧急数据 MSG_PEEK接收缓冲区数据但不清除缓冲区,偷看缓冲区数据
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已收字节数
char* bufp=(char*)buf; //char指针指向buf
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR) //读信号中断
{
continue;
return -1;
}
}
else if(nread==0) //对等方关闭
{
return count-nleft;
}
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t written(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
{
continue;
return -1;
}
}
else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
//仅从套接口缓冲区拿数据放到buf,未将数据从套接口缓冲区清除,而read()会将数据清除
//因为可能因为异常返回-1,所以返回值定义成有符号数
ssize_t recv_peek(int sockfd,void* buf,size_t len)
{
while(1)
{
int ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-1&&errno==EINTR)
continue;
else
return ret;
}
}
2.readline()的实现
实现按行读取,知道遇到\n算一条消息,可以解决粘包问题,只要遇到\n表示前面是条合法消息,比如ftp协议 。
//因为readline()用recvpeek()封装的,所以readline()只能用于套接口
ssize_t readline(int sockfd,void* buf,size_t maxline)
{
int ret;
char* bufp=buf; //因为这个缓冲区开头函数中一直在变,可以保留buf,创建一个buf的副本
int nleft=maxline;
int nread;
while(1)
{
ret=recv_peek(sockfd,bufp,nleft);
if(ret<0)
return ret; //此处不用再判断是否是信号中断,因为在recv()以判断
else if(ret==0)
return ret;//对方关闭套接口。这里与readn()不同,readn()只要返回的ret不等于指定字节数就说明对方关闭了套接口
nread=ret;
int i;
for(i=0;i<nread;++i)
{
if(bufp[i]=='\n') //偷窥到的字符串中含有行的终止符'\n'
{
ret=readn(sockfd,bufp,i+1); //用readn()把\n和她前面的字符读走
if(ret!=i+1)
error_handling("readline");
return ret;
}
}
nleft-=nread; //走到这里,是偷窥到的字符串中没有\n,那就将已经偷窥到的放进buf,然后接着上次偷窥到的位置继续读,找\n
ret=readn(sockfd,bufp,nread);
if(ret<0)
error_handling("readline_readn");
bufp+=nread;
}
return -1;
}
3.用readline()实现客户、服务器解决粘包问题
//客户端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已收字节数
char* bufp=(char*)buf; //char指针指向buf
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR) //读信号中断
{
continue;
return -1;
}
}
else if(nread==0) //对等方关闭
{
return count-nleft;
}
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t written(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
{
continue;
return -1;
}
}
else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
//仅从套接口缓冲区拿数据放到buf,未将数据从套接口缓冲区清除,而read()会将数据清除
//因为可能因为异常返回-1,所以返回值定义成有符号数
ssize_t recv_peek(int sockfd,void* buf,size_t len)
{
while(1)
{
int ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-1&&errno==EINTR)
continue;
else
return ret;
}
}
//因为readline()用recvpeek()封装的,所以readline()只能用于套接口
ssize_t readline(int sockfd,void* buf,size_t maxline)
{
int ret;
char* bufp=buf; //因为这个缓冲区开头函数中一直在变,可以保留buf,创建一个buf的副本
int nleft=maxline;
int nread;
while(1)
{
ret=recv_peek(sockfd,bufp,nleft);
if(ret<0)
return ret; //此处不用再判断是否是信号中断,因为在recv()以判断
else if(ret==0)
return ret;//对方关闭套接口。这里与readn()不同,readn()只要返回的ret不等于指定字节数就说明对方关闭了套接口
nread=ret;
int i;
for(i=0;i<nread;++i)
{
if(bufp[i]=='\n') //偷窥到的字符串中含有行的终止符'\n'
{
ret=readn(sockfd,bufp,i+1); //用readn()把\n和她前面的字符读走
if(ret!=i+1)
error_handling("readline");
return ret;
}
}
nleft-=nread; //走到这里,是偷窥到的字符串中没有\n,那就将已经偷窥到的放进buf,然后接着上次偷窥到的位置继续读,找\n
ret=readn(sockfd,bufp,nread);
if(ret<0)
error_handling("readline_readn");
bufp+=nread;
}
return -1;
}
int main()
{
int sock;
sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock<0)
error_handling("sock");
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");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
error_handling("connect");
struct sockaddr_in localaddr;
socklen_t addrlen=sizeof(localaddr);
if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen)<0)
error_handling("getsockname");
printf("ip=%s port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
// struct packet sendbuf;
// struct packet recvbuf;
char sendbuf[1024]={0};
char recvbuf[1024]={0};
int n;
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
written(sock,&sendbuf,strlen(sendbuf));
int ret=readline(sock,recvbuf,1024);
if(ret==-1)
error_handling("readline");
else if(ret==0)
{
printf("client close\n");
break;}
fputs(recvbuf,stdout);
memset(&sendbuf,0,sizeof(sendbuf));
memset(&sendbuf,0,sizeof(sendbuf));
}
close(sock);
return 0;
}
//服务器端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已收字节数
char* bufp=(char*)buf; //char指针指向buf
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR) //读信号中断
{
continue;
return -1;
}
}
else if(nread==0) //对等方关闭
{
return count-nleft;
}
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t written(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
{
continue;
return -1;
}
}
else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
//仅从套接口缓冲区拿数据放到buf,未将数据从套接口缓冲区清除,而read()会将数据清除
//因为可能因为异常返回-1,所以返回值定义成有符号数
ssize_t recv_peek(int sockfd,void* buf,size_t len)
{
while(1)
{
int ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-1&&errno==EINTR)
continue;
else
return ret;
}
}
//因为readline()用recvpeek()封装的,所以readline()只能用于套接口
ssize_t readline(int sockfd,void* buf,size_t maxline)
{
int ret;
char* bufp=buf; //因为这个缓冲区开头函数中一直在变,可以保留buf,创建一个buf的副本
int nleft=maxline;
int nread;
while(1)
{
ret=recv_peek(sockfd,bufp,nleft);
if(ret<0)
return ret; //此处不用再判断是否是信号中断,因为在recv()以判断
else if(ret==0)
return ret;//对方关闭套接口。这里与readn()不同,readn()只要返回的ret不等于指定字节数就说明对方关闭了套接口
nread=ret;
int i;
for(i=0;i<nread;++i)
{
if(bufp[i]=='\n') //偷窥到的字符串中含有行的终止符'\n'
{
ret=readn(sockfd,bufp,i+1); //用readn()把\n和她前面的字符读走
if(ret!=i+1)
error_handling("readline");
return ret;
}
}
nleft-=nread; //走到这里,是偷窥到的字符串中没有\n,那就将已经偷窥到的放进buf,然后接着上次偷窥到的位置继续读,找\n
ret=readn(sockfd,bufp,nread);
if(ret<0)
error_handling("readline_readn");
bufp+=nread;
}
return -1;
}
int main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd<0)
error_handling("socket");
// struct packet sendbuf;
//struct packet recvbuf;
// memset(&sendbuf,0,sizeof(sendbuf));
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);
/***************************************************************/
//这三句的作用是:当客户端关闭后,可以立即重启,而不需要经过一段时间的TIME_WAIT的等待
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
error_handling("socket");
/***************************************************************/
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
error_handling("bind");
if(listen(listenfd,SOMAXCONN)<0)
error_handling("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen=sizeof(peeraddr);
int conn;
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
error_handling("accept");
printf("IP=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));//打印对等方的IP地址和端口号
char recvbuf[1024];
while(1)
{
memset(&recvbuf,0,sizeof(recvbuf));
int ret=readline(conn,&recvbuf,1024);
if(ret==-1)
{
error_handling("readn");
}
else if(ret==0)
{
printf("client close\n");
break;
}
fputs(recvbuf,stdout);
written(conn,recvbuf,strlen(recvbuf));
}
close(conn);close(listenfd);
return 0;
}
二、getsockname()和getpeername()
sock 连接本地与对等方若要获取套接口本地这端的地址用getsockname()
若要获取对等方的getpeername()
例如:
再本问上面的例子代码,的客户端的connect()后添加
struct sockaddr_in localaddr;
socklen_t addrlen=sizeof(localaddr);
if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen)<0)
error_handling("getsockname");
printf("ip=%sport=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
客户端代码其他部分以及服务器端程序不变,运行,客户端显示的本地端口号与服务器端显示的客户端端口号相同
注意:(1)getsockname()和getpeername()都要在建立好的连接上
(2) 客户端要在正确调用connect()后才能用,服务器端要在正确调用accept()后才能用
(3) (struct sockaddr*)&peeraddr和&peerlen两个参数要么都空指针,要么都不空
三、geyhostname()、gethostbyname()
geyhostname() 获取主机名
gethostbyname() 通过主机名获取主机上所有IP地址
举例说明gethostname()用法
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
int main()
{
char host[100]={0};
if(gethostname(host,sizeof(host))<0)
return -1;
printf("%s\n",host);
return 0;
}