recv
int recv(SOCKET s, char FAR, int len, int flags)
recv只能用于套接口io,不能用于文件io,而read函数则都可以。
recv多了个flags选项,通过这个选项能够执行接收行为,但是一般都填0
主要两个参数为
- MSG_OOB 接收通过紧急指针发送的数据
- MSG_PEEK 接收缓冲区的数据,并不清楚缓冲区的数据,而read函数读取之后会清楚缓冲区数据。
封装一个recv_peek
ssize_t recv_peek(int socked, void *buf, size_t len){
while(1){
int ret = recv(sockfd, buf, len, MSG_PEEK);
if(ret == -1&&errno == EINTR){
continue;//此时是中断的情况
} else if(ret < 4){
printf("client close\n");
break;
}
}
}
使用上面封装的recv_peek函数来实现readline功能
readline实现按行读取,遇到换行符\n即停止读取。
readline也能解决粘包问题。
ssize_t readline(int sockfd, void *buf, size_t maxline){
int ret;
int nread;
char *bufp = buf;
int nleft = maxline;
while(1){
ret = recv_peek(sockfd, bufp, nleft);//这里虽然读取的数据,但是不会影响到缓冲区
if(ret < 0){
return ret;
} else if(ret == 0){
return ret;
}
nread = ret;//读了多少个数据
int i;
for(i = 0; i < nread; i++){
if(bufp[i] == '\n'){//找到一行的末尾
ret = readn(sockfd, bufp, i+1);
if(ret != i+1){
exit(EXIT_FAILURE);
}
return ret;
}
}
if(nread > nleft){
exit(EXIT_FAILURE);
}
nleft -= nread
ret = readn(sockfd, bufp, nread);//这里不是很懂,为什么要在使用一边readn呢?为什么不直接使用上面的bufp呢?
if(ret != nread){
exit(EXIT_FAILURE);
}
bufp += nread;
}
return -1;
}
使用readline解决粘包
修改服务器端
void do_service(int conn){
char recvbuf[1024];
while(1){
memset(recvbuf, 0, sizeof(recvbuf));
int ret = readline(conn, recvbuf, 1024);
if(ret == -1){
ERR_EXIT("readline");
}
if(ret == 0){
printf("client close\n");
break;
}
fputs(recvbuf, stdout);
writen(conn, recvbuf, strlen(recvbuf));
}
}
修改客户端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
struct packet{
int len;
char buf[1024];
}
ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
size_t nleft = count;//剩余字节数
ssize_t nread;//已接受的字节数
char *bufp = (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;//这里传输的是整个函数已经接收了多少个。
} else {
bufp += nread;//更新
nleft -= nread;
}
}
return count;
}
ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
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 (nread == 0){
continue;//如果等于零,对于write操作等于无事发生
} else {
bufp += nwritten;//更新
nleft -= nwritten;
}
}
return count;
}
int main(){
int sock;
if(sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0
{
ERR_EXIT("socket");//报错误的宏
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));//全部填充0
servaddr.sin_family = AF_INET;
servaddr.sin_prot = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(sock, (struct sockaddr*)servaddr, sizeof(servaddr)) < 0){
ERR_EXIT("connect");
}
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
while(fgets(sendbuf, sizeof(sendbuf), stdin)!= NULL){//此处使用结构体里面的成员
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n);//此处多此一举是为了统一字节序转换为网络字节序,此处是包体的长度
writen(sock, sendbuf, strlen(sendbuf));//包头有四个字节
int ret = readline(sock, recvbuf, sizeof(recvbuf));
if{ret == -1 }{
ERR_EXIT("readline");
} else if(ret == 0){
printf("client_close\n");
break;
}
fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sock);
return 0;
}
getsockname
可以在客户端上面使用,获取自己的地址和端口号
sturct sockaddr_in localaddr;
if(getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0){
ERROR_EXIT("connect");
}
printf("ip = %s port = %d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));//inet_ntoa网络地址转换为点分十进制地址。
getpeername
通过一个已连接的套接口,返回对方的地址和端口号
getpeername(sock, (struct sockaddr*)&peeraddr, &addrlen)
gethostname
获取主机名
char host[100] = {0};
if(gethostname(host, sizeof(host))<0)
{
ERR_EXIT("gethostname");
}
gethostbyname
通过主机名,获取主机上所有的ip地址
上面的 h_addr_list为主机ip列表
gethostbyname的输入参数可以是ip地址和域名
#include <netdb.h>
char host[100] = {0};
if(gethostname(host, sizeof(host))<0)//先获取主机名
{
ERR_EXIT("gethostname");
}
struct hostent *hp;
if((hp = gethostbyname(host)) == NULL){
ERR_EXIT("gethostbyname");
}
int i = 0;//这里别忘记初始化为零
while(hp->h_addr_list[i] != NULL){
printf("%s\n", inet_ntoa(*(struct in_addr*)hp->h_addr_list[i])); //in_addr是网络地址,转换为点分十进制地址
i++;
}
gethostaddr
根据本机的地址结构,获取所有的地址保存在结构体当中。