版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/buhuiguowang/article/details/81590597
socket 长连接PK短连接、P2P,流协议
- 最开始并不能完成服务器一对多的要求??
- 虽然客户端与服务器建立了连接,但服务器是执行不到accept函数的,一直停在循环里,只是单进程。所以这是前面服务器不能支持客户端多并发的原因。
- 这里客户端能连接的原因是因为listen函数,告诉linux内核中的TCP/IP协议族要维护一系列的客户端连接。因此客户端实际上是 linux内核中的TCP/IP为其建立的TCP三次握手,并没有拿到服务器的连接。
- 解决的办法就是前面提过的调用fork()函数,建立多进程。
服务器
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
/*
man socket
int socket(int domain, int type, int protocol);
*/
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("fun socket\n");
exit(0);
}
/*
man 7 ip
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//Internet address.
struct in_addr
{
uint32_t s_addr; //address in network byte order
};
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8001);
srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
/*
man 2 bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
if(bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
{
perror("fun socket\n");
exit(0);
}
/*
man listen
int listen(int sockfd, int backlog);
*/
//Once you call listen function,this socket will become passive,it means that can only recive and doesn't send actively
if(listen(sockfd, SOMAXCONN) < 0)
{
perror("fun listen\n");
exit(0);
}
//struct sockaddr peeraddr; //Universal ip
//socklen_t peerlen;
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//man 2 accept
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
struct sockaddr_in peeraddr; //tcpip address structure
socklen_t peerlen = sizeof(peerlen);
unsigned int conn = 0;
while(1)
{
//Accept will return a new connection, it is
//If there is no client connection, it will block here
conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
if(conn == -1)
{
perror("fun conn\n");
exit(0);
}
printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
int pid = fork();
if(pid == 0) //this process is child process
{
close(sockfd); // child process doesn't need to listen
char rvbuf[1024] = {0};
while(1)
{
/*
man 2 read
size_t read(int fd, void *buf, size_t count);
*/
int ret = read(conn, rvbuf, sizeof(rvbuf));
if(ret == 0)
{
//If the other party is already closed during the reading process,tcpip will return a zero packet
printf("peer is already closed\n");
exit(0);
}
else if(ret < 0)
{
printf("read data failure\n");
exit(0);
}
fputs(rvbuf, stdout); // server receives the data and prints the screen.
/*
man 2 write
size_t write(int fd, const void *buf, size_t count);
*/
write(conn, rvbuf, sizeof(ret)); //server sends pocket back
}
}
else if(pid > 0) //parent process
{
close(conn); //parent doesn't need connection
}
else
{
printf("create fork failure");
exit(0);
}
}
return 0;
}
- 那么这里的子进程又是什么时候结束呢??
- 这就涉及到长链接和短链接的知识了。
- 一般来说是客户机决定连接是长连接还是短连接
-
长连接:
- 客户端在建立了一个连接后,还没有断开,又建立了一个连接即客户端和服务器的连接不断开,就是长连接。比如说上面的子进程
-
短连接
- 就是说客户端作完一个业务就和服务器断开,然后在需要的时候就再和服务器连接。 比如说上面的父进程。
P2P服务程序开发
- 原理图:
- 代码实现:
服务器
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
/*
man socket
int socket(int domain, int type, int protocol);
*/
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("fun socket\n");
exit(0);
}
/*
man 7 ip
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//Internet address.
struct in_addr
{
uint32_t s_addr; //address in network byte order
};
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8002);
srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
/*
man 2 bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
if(bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
{
perror("fun socket\n");
exit(0);
}
/*
man listen
int listen(int sockfd, int backlog);
*/
//Once you call listen function,this socket will become passive,it means that can only recive and doesn't send actively
if(listen(sockfd, SOMAXCONN) < 0)
{
perror("fun listen\n");
exit(0);
}
//struct sockaddr peeraddr; //Universal ip
//socklen_t peerlen;
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//man 2 accept
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
struct sockaddr_in peeraddr; //tcpip address structure
socklen_t peerlen = sizeof(peerlen);
unsigned int conn = 0;
//Accept will return a new connection, it is
conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
if(conn == -1)
{
perror("fun conn\n");
exit(0);
}
printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
char revbuf[1024];
char sendbuf[1024];
int pid = 0;
pid = fork();
if(pid>0)
{
while(1)
{
memset(revbuf,0, sizeof(revbuf));
/*
man 2 read
size_t read(int fd, void *buf, size_t count);
*/
int ret = read(conn, revbuf, sizeof(revbuf));
if(ret == 0)
{
//If the other party is already closed during the reading process,tcpip will return a zero packet
printf("peer is already closed\n");
exit(0);
}
else if(ret < 0)
{
printf("read data failure\n");
exit(0);
}
fputs(revbuf, stdout); // server receives the data and prints the screen
memset(revbuf, 0, sizeof(revbuf));
}
}
else if(pid == 0) //parent process
{
memset(sendbuf, 0, sizeof(sendbuf));
while(fgets(sendbuf,sizeof(sendbuf), stdin) != NULL )
{
printf("sendbuf is:%s", sendbuf);
write(conn, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
}
close(sockfd);
close(conn);
return 0;
客户端
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
/*
man socket
int socket(int domain, int type, int protocol);
*/
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("fun socket\n");
exit(0);
}
/*
man 7 ip
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//Internet address.
struct in_addr
{
uint32_t s_addr; //address in network byte order
};
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8002);
srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(sockfd, (struct sockaddr *)(&srvaddr), sizeof(srvaddr)) < 0)
{
perror("fun socket\n");
exit(0);
}
char revbuf[1024];
char sendbuf[1024];
int pid = 0;
pid = fork();
if(pid>0)
{
memset(sendbuf, 0, sizeof(sendbuf));
while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
printf("sendbuf is :%s", sendbuf);
write(sockfd, sendbuf, strlen(sendbuf)); //server send message back
memset(sendbuf, 0, sizeof(sendbuf));
}
}
else if(pid==0)
{
memset(revbuf, 0, sizeof(revbuf));
while(1)
{
read(sockfd, revbuf, sizeof(revbuf));
fputs(revbuf, stdout);
memset(revbuf, 0, sizeof(revbuf));
}
}
close(sockfd);
return 0;
}
- 这里的客户端是怎么退出的呢:
- 客户端主动退出调用close函数,或者客户端挂掉,tcpip协议会偷偷的向服务器发送FIN数据段。FIN字段就相当于一个\0,当服务器读到FIN字段的时候,服务器就就知道客户端断开了。
- 利用信号来实现客户端断开,服务器退出。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
void handle (int num)
{
printf("recv num:%d\n", num);
exit(0);
}
int main()
{
//Installation signal
signal(SIGUSR1, handle);
/*
man socket
int socket(int domain, int type, int protocol);
*/
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("fun socket\n");
exit(0);
}
/*
man 7 ip
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//Internet address.
struct in_addr
{
uint32_t s_addr; //address in network byte order
};
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8002);
srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
/*
man 2 bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
if(bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
{
perror("fun socket\n");
exit(0);
}
/*
man listen
int listen(int sockfd, int backlog);
*/
//Once you call listen function,this socket will become passive,it means that can only recive and doesn't send actively
if(listen(sockfd, SOMAXCONN) < 0)
{
perror("fun listen\n");
exit(0);
}
//struct sockaddr peeraddr; //Universal ip
//socklen_t peerlen;
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//man 2 accept
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
struct sockaddr_in peeraddr; //tcpip address structure
socklen_t peerlen = sizeof(peerlen);
unsigned int conn = 0;
//Accept will return a new connection, it is
conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen);
if(conn == -1)
{
perror("fun conn\n");
exit(0);
}
printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
char revbuf[1024];
char sendbuf[1024];
int pid = 0;
pid = fork();
if(pid>0)
{
while(1)
{
memset(revbuf,0, sizeof(revbuf));
/*
man 2 read
size_t read(int fd, void *buf, size_t count);
*/
int ret = read(conn, revbuf, sizeof(revbuf));
if(ret == 0)
{
//If the other party is already closed during the reading process,tcpip will return a zero packet
printf("peer is already closed\n");
break;
}
else if(ret < 0)
{
printf("read data failure\n");
exit(0);
}
fputs(revbuf, stdout); // server receives the data and prints the screen
memset(revbuf, 0, sizeof(revbuf));
}
close(conn);
kill(pid, SIGUSR1);
sleep(1);
}
else if(pid == 0) //parent process
{
memset(sendbuf, 0, sizeof(sendbuf));
while(fgets(sendbuf,sizeof(sendbuf), stdin) != NULL )
{
printf("sendbuf is:%s", sendbuf);
write(conn, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
}
close(sockfd);
return 0;
}
- 如果服务器断开客户端怎么退出呢
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void handle (int num)
{
printf("recv num:%d\n", num);
exit(0);
}
int main()
{
signal(SIGUSR1, handle);
/*
man socket
int socket(int domain, int type, int protocol);
*/
int sockfd = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("fun socket\n");
exit(0);
}
/*
man 7 ip
struct sockaddr_in
{
sa_family_t sin_family; //address family: AF_INET
in_port_t sin_port; //port in network byte order
struct in_addr sin_addr; //internet address
};
//Internet address.
struct in_addr
{
uint32_t s_addr; //address in network byte order
};
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
*/
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(8002);
srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(sockfd, (struct sockaddr *)(&srvaddr), sizeof(srvaddr)) < 0)
{
perror("fun socket\n");
exit(0);
}
char revbuf[1024];
char sendbuf[1024];
int pid = 0;
pid = fork();
if(pid>0)
{
memset(sendbuf, 0, sizeof(sendbuf));
while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
printf("sendbuf is :%s", sendbuf);
write(sockfd, sendbuf, strlen(sendbuf)); //server send message back
memset(sendbuf, 0, sizeof(sendbuf));
}
}
else if(pid==0)
{
memset(revbuf, 0, sizeof(revbuf));
while(1)
{
int ret = 0;
ret = read(sockfd, revbuf, sizeof(revbuf));
if(ret<0)
{
printf("read err\n");
break;
}
if(ret==0)
{
printf("server closed\n");
break;
}
fputs(revbuf, stdout);
memset(revbuf, 0, sizeof(revbuf));
}
close(sockfd);
kill(getppid(), SIGUSR1);
exit(0);
}
return 0;
}
tcpip协议流与粘包
- 比如说发送两个消息:M1,M2
- 一个是流模式即无边界,这就可能导致服务器B可能同时收到M1、M2;也有可能在收到M1的同时,收到M2的一部分,下一次收到M2.2,也有可能收到M1.1,在下一次收到M2和M1.2。这就导致了读出现了很大的bug。前面read没有出问题只是在局域网内比较快,没有出现问题,但一旦放到公网,这里的问题就很大了
- 解决办法:人为的定义流的边界
- 目前主流的方法有两种:
- 1:加 \r 或 \n
- 2.加自定义报文:在数据的前面加报头,长度为4个字节
- 产生粘包的原因
- 1 SO_SNDBUF 套接字本身有缓冲区(发送缓冲区、接收缓冲区)
- 2 tcp传送的端mss 大小限制
- 3 链路层也有MTU大小限制,如果包大于>MTU(MTU是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。)要在IP层进行分片,导致消息分割。
- 4 tcp的流量控制和拥塞控制,也可能导致粘包。
- 5 tcp延迟发送机制 等等
- 结论:tcp/ip协议,在传输层没有处理粘包问题。
- 解决办法:
- 1 定长包;
- 2 包尾加 \r \n (ftp)
- \n作为边界协议:
- (ssize_t recv(int sockfd, void *buf, size_t len, int flags);)
- 后面解释这个函数。
- \n作为边界协议:
- 3 包头加上包体长度(加4个字节 )
- 4 更加复杂的应用层协议
-
readn, writen,readline函数
-
为什么要封装一个readn 函数和 writen 函数,现有的read 函数和 write 含有有什么缺陷?
- 这个是因为在调用read(或 write)函数的时候,读(写)一次的返回值可能不是我们想到读的字节数(即read函数中的 count 参数)就前面讲道的,这经常在读取管道,或者网络数去时出现。
- readn函数
- readn保证在没有遇到EOF的情况下,一定可以读取n个字节。它的返回值有三种:
a) >0,表示成功读取的字节数,如果小于n,说明中间遇到了EOF;
b)==0 表示一开始读取就遇到EOF;
c) -1 表示错误(这里的errno绝对不是EINTR)。readn函数功能:读满n个字节才返回 int readn(int fd, void *vptr, size_t n) { size_t nleft = n; //readn函数还需要读的字节数 ssize_t nread = 0; //read函数读到的字节数 unsigned char *ptr = (char *)vptr; //指向缓冲区的指针 while (nleft > 0) { nread = read(fd, ptr, nleft); if (-1 == nread) { if (EINTR == errno) nread = 0; else return -1; } else if (0 == nread) { break; } nleft -= nread; ptr += nread; } return n - nleft; }
- writen函数
- writen函数保证一定写满n个字节,返回值:
a)n 表示写入成功n个字节
b)-1 写入失败(这里也没有EINTR错误)writen函数功能:写满n个字节才返回 writen代码实现: int writen(int fd, const void *vptr, size_t n) { size_t nleft = n; //writen函数还需要写的字节数 ssize_t nwrite = 0; //write函数本次向fd写的字节数 const char* ptr = vptr; //指向缓冲区的指针 while (nleft > 0) { if ((nwrite = write(fd, ptr, nleft)) <= 0) { if (nwrite < 0 && EINTR == errno) nwrite = 0; else return -1; } nleft -= nwrite; ptr += nwrite; } return n; }
*加报头
/*********************************************************************************
* Copyright: (C) 2018 anzhihong<[email protected]>
* All rights reserved.
*
* Filename: socket_server.c
* Description: This file
*
* Version: 1.0.0(2018年08月04日)
* Author: anzhihong <[email protected]>
* ChangeLog: 1, Release initial version on "2018年08月04日 09时00分22秒"
*
********************************************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
struct packet
{
int len;
char buf[1024];
};
ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count; //the number of bytes that need to read
ssize_t nread; //the number of bytes that read
char *bufp = (char*)buf;
while(nleft>0)
{
if((nread = read(fd, bufp, nleft)) < 0) //Rutrn the bytes that read when read successfully
{
if(errno == EINTR) //If it is interrupted by a signal, it is not an error. Read again
continue;
return -1;
}
else if(nread == 0) //The data read is empty and returns 0, indicating that the connection is disconnected
return (count - nleft);
bufp += nread;//读完nread字节数据之后,指针往下偏移这个量,下次的&bufd就是从这里开始
nleft -= nread; //这里的设定是为了多次把nleft读取完,只要nleft还有即>0,就重复这个循环
}
return count; //全部执行正确返回读取的count,一开始是4,之后是len
}
ssize_t writen(int fd, 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;
}
void do_service(int conn)
{
struct packet recvbuf;
int n;
while(1)
{
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = 0;
ret = readn(conn, &recvbuf.len, 4);
if(ret == -1)
printf("read erro\n");
else if(ret < 4)
{
printf("client is closed\n");
break;
}
n = ntohl(recvbuf.len); //将网络字节序转换为机器字节序 这里 recvbuf.len 的长度等于n = strlen(sendbuf.buf); sendbuf.len = htonl(n) 的长度
ret = readn(conn, recvbuf.buf, n);
if(ret == -1)
printf("read erro\n");
else if(ret < n)
{
printf("client is closed\n");
break;
}
fputs(recvbuf.buf, stdout);
writen(conn, &recvbuf, 4+n);
memset(&recvbuf, 0, sizeof(recvbuf));
}
}
int main(void)
{
int sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
printf("creat socket failure\n");
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8001);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_aton("127.0.0.1", &servaddr.sin_addr);
int optval = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval,sizeof(optval)) < 0 )
printf("setsockopt erro\n");
if(bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
printf("bind erro\n");
if(listen(sockfd, SOMAXCONN) < 0)
printf("listen erro\n");
struct sockaddr_in peeraddr; //tcpip address structure
socklen_t peerlen = sizeof(peerlen);
unsigned int conn = 0;
pid_t pid;
while(1)
{
if((conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0)
printf("accept erro\n");
printf("peeraddr:%s\n peerport:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid = fork();
if(pid == 0)
{
close(sockfd);
do_service(conn);
exit(EXIT_SUCCESS);
}
else if(pid > 0)
{
close(conn);
}
else
{
printf("creat fork failure");
exit(0);
}
}
return 0;
}
/*********************************************************************************
* Copyright: (C) 2018 anzhihong<[email protected]>
* All rights reserved.
*
* Filename: socket_client.c
* Description: This file
*
* Version: 1.0.0(2018年08月04日)
* Author: anzhihong <[email protected]>
* ChangeLog: 1, Release initial version on "2018年08月04日 10时03分33秒"
*
********************************************************************************/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
struct packet
{
int len;
char buf[1024];
};
ssize_t readn(int fd, void *buf, size_t count)
{
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);
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, 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;
}
int main(void)
{
int sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("creat socket failure");
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8001);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
unsigned int conn = 0;
if(connect(sockfd, (struct sockaddr*)(&servaddr), sizeof(servaddr)) < 0)
ERR_EXIT("accept erro");
struct packet sendbuf;
struct packet recvbuf;
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
int n = 0;
while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
{
printf("sendbuf is :%s\n", sendbuf.buf);
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n);
writen(sockfd, &sendbuf, 4+n);
int ret = 0;
ret = readn(sockfd, &recvbuf.len, 4);
if(ret == -1)
ERR_EXIT("read errno");
else if(ret < 4)
{
printf("client is closed\n");
break;
}
n = ntohl(recvbuf.len);
ret = readn(sockfd, recvbuf.buf, n);
if(ret == -1)
ERR_EXIT("read errno");
else if(ret < n)
{
printf("client is closed");
break;
}
fputs(recvbuf.buf, stdout);
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
}
close(sockfd);
return 0;
}
- readline函数
- readline函数:ssize_t readline(int fd, void *usrbuf, size_t maxlen),它的返回值:
a)错误返回-1,不包括EINTR;
b)取过程中碰到\n;
c)没有碰到\n,而是读满maxlen-1个字节。
readline函数功能:读到'\n'或者读满缓冲区才返回
readline函数实现:
static ssize_t readch(int fd, char *ptr)
{
static int count = 0;
static char* read_ptr = 0;
static char read_buf[1024*4] = {0};
if (count <= 0)
{
again:
count = read(fd, read_buf, sizeof(read_buf));
if (-1 == count)
if (EINTR == errno)
goto again;
else
return -1;
else if (0 == count)
return 0;
read_ptr = read_buf;
}
count--;
*ptr = *read_ptr++;
return 1;
}
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
ssize_t i = 0;
ssize_t ret = 0;
char ch = '\0';
char* ptr = NULL;
ptr = (char *)vptr;
for (i = 1; i < maxlen; ++i)
{
ret = readch(fd, &ch);
if (1 == ret)
{
*ptr++ = ch;
if ('\n' == ch)
break;
}
else if (0 == ret)
{
*ptr = 0;
return i-1;
}
else
return -1;
}
*ptr = 0;
return i;
}
- recv函数
- 函数原型:
- int recv( In SOCKET s, Out char *buf, In int len, In int flags);
- int flag 的参数功能:
- MSG_DONTROUTE 绕过路由表查找。
MSG_DONTWAIT 仅本操作非阻塞。
MSG_OOB 发送或接收带外数据。 //常用
MSG_PEEK 窥看外来消息。 // 常用(数据包的提前预读)
MSG_WAITALL 等待所有数据。
返回值:
若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。如果发生错误,返回-1,应用程序可通过perror()获取相应错误信息
- 加\n
/*********************************************************************************
* Copyright: (C) 2018 anzhihong<[email protected]>
* All rights reserved.
*
* Filename: socket_server.c
* Description: This file
*
* Version: 1.0.0(2018年08月05日)
* Author: anzhihong <[email protected]>
* ChangeLog: 1, Release initial version on "2018年08月05日 08时23分10秒"
*
********************************************************************************/
#include <unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nleft=count;//还需要读的字节数
ssize_t nread;//已经接收字节数
char *bufp=(char *)buf;
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR)//read是可中断的,所以当被外来信号中断打断时,不算错误,任然继续执行
continue;
return -1;
}
else if(nread==0)//对等方关闭
return count-nleft;//读到EOF,对方关闭
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd,void *buf,size_t count)
{
size_t nleft=count;//剩余发送字节数
ssize_t nwritten;//已经发送字节数
char *bufp=(char *)buf;
while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)//被中断
continue;
return -1;
}
else if(nwritten==0)//对等方关闭
continue;//读到EOF,对方关闭
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
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)//操作被信号中断,recv认为链接正常,继续执行。
continue;
return ret;
}
}
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
int ret = 0;
int nread;
char *bufp = buf;
int nleft = maxline; //读取遇到\n返回,不会超过maxline
while(1)
{
ret = recv_peek(sockfd, bufp, nleft);
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) //偷窥到的数据不能大于maxline
exit(EXIT_FAILURE);
nleft -= nread; //剩余字节数
ret = readn(sockfd, bufp, nread); //将已经读到数据nread从缓冲区移除
if(ret != nread)
exit(EXIT_FAILURE);
bufp += nread; //继续偷窥
}
return 1;
}
void do_service(int conn)
{
char recvbuf[1024];
while(1)
{
memset(recvbuf, 0, sizeof(recvbuf)); //初始化recvbuf
int ret=readline(conn,recvbuf,1024);//一行一行接收数据。
if(ret == -1)
printf("readline");
if(ret == 0)
{
printf("client close");
break;
}
fputs(recvbuf, stdout);
writen(conn, recvbuf, strlen(recvbuf));
}
}
int main(void)
{
int listenfd;
/* if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
printf("socket err\n");
//IPV4地址结构
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;//地址家族
servaddr.sin_port=htons(8001);//端口,主机转网络
/*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
inet_aton("127.0.0.1",&servaddr.sin_addr);*/
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
//地址复用
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
printf("setsockopt err\n");
//绑定端口号,ip地址
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
printf("bind err\n");
//监听
if(listen(listenfd,SOMAXCONN)<0)
printf("bind err\n");
struct sockaddr_in peeraddr;
socklen_t peerlen =sizeof(peeraddr);//typedef int socklen_t
int conn;//已连接套接字
pid_t pid;
while(1)
{
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
printf("conn err\n");
printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
pid = fork();
if(pid==-1)
printf("fork err\n");
if(pid==0)
{
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);//将子进程退出,要不它还会fork()
}
else
close(conn);
}
return 0;
}
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
ssize_t readn(int fd,void *buf,size_t count)
{
//ssize_t=int,size_t=unsigned int
//接收count个字节数
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;//读到EOF,对方关闭
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd,void *buf,size_t count)
{
size_t nleft=count;//剩余发送字节数
ssize_t nwritten;//已经发送字节数
char *bufp=(char *)buf;
while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)//被中断
continue;
return -1;
}
else if(nwritten==0)//对等方关闭
continue;//读到EOF,对方关闭
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
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)//操作被信号中断,recv认为链接正常,继续
continue;
return ret;//返回读取的字节数
}
}
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
int ret;
int nread;
char *bufp=buf;
int nleft=maxline;//读取遇到\n返回,不会超过maxline
while(1){
ret=recv_peek(sockfd,bufp,nleft);
if(ret<=0)
return ret;
nread=ret;
int i;//接下来判断接收的缓冲区是否有\n
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){
//偷窥到的数据不能大于maxline
exit(EXIT_FAILURE);
}
nleft-=nread;//剩余字节数
ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//继续偷窥
}
return 1;
}
//发送定长包
int main()
{
int sock;
/*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
printf("socket err\n");
//IPV4地址结构
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;//地址家族
servaddr.sin_port=htons(8001);//端口,主机转网络
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
printf("connect err\n");
//getsockname
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin) !=NULL){//从文件读取一行,送到缓冲区
writen(sock,&sendbuf,strlen(sendbuf));//将接收到的数据发送出去
int ret=readline(sock,recvbuf,sizeof(recvbuf));//函数从打开的文件,设备中读取数据
if(ret==-1)
{
printf("readline err");
}
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;
}