//commsocket.h
#ifndef __COMMSOCKET__H
#define __COMMSOCKET__H
#ifdef __cplusplus
extern ‘C‘
{
#endif
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0)
typedef struct _Handle
{
int sockfd;
int conntime;
int sendtime;
int recvtime;
}Handle;
typedef struct _packet
{
int len;
char buf[1024];
}packet;
//////////////////////////////////////////////////
// base functions
ssize_t readn(int fd, void *buf, size_t count);
ssize_t writen(int fd, const void *buf, size_t count);
ssize_t recv_peek(int sockfd, void *buf, size_t len);
ssize_t readline(int sockfd, void *buf, size_t maxline);
int activate_nonblock(int fd);
int deactivate_nonblock(int fd);
/////////////////////////////////////////////////////
//超时处理函数
int connect_timeout(int fd, struct sockaddr_in *addr, int wait_seconds);
int accept_timeout(int fd, struct sockaddr_in *addr, int wait_seconds);
int read_timeout(int fd, int wait_seconds);
int write_timeout(int fd, int wait_seconds);
///////////////////////////////////////////////////////
int cliet_init(Handle **handle, int conntime, int sendtime, int recvtime);
int cliet_getconn(Handle *handle, char *ip, int port, int *connfd);
int client_send(Handle *handle, packet* sendbuf);
int client_recv(Handle *handle, packet* recvbuf);
////////////////////////////////////////////////////
//server functions
int server_init(int port, int *listenfd);
int server_accept(int listenfd, int *connfd, struct sockaddr_in *addr, int timeout);
int server_recv(int connfd, packet* recvbuf, int timeout);
int server_send(int connfd, packet* sendbuf, int timeout);
#ifdef __cpluspluse
}
#endif
#endif
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "commsocket.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)
continue;
return -1;
}
else if (nread == 0)
return count - nleft;
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten = 0;
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;
}
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;
return ret;
}
}
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);
if (ret != nread)
exit(EXIT_FAILURE);
bufp += nread;
}
return -1;
}
/**
* activate_noblock - 设置I/O为非阻塞模式
* @fd: 文件描符符
*/
int activate_nonblock(int fd)
{
int ret = 0;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
{
ret = flags;
printf("activate_nonblock() Err:%d", ret);
return ret;
}
flags |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -1)
{
printf("activate_nonblock() Err : %d", ret);
return ret;
}
return ret;
}
/**
* deactivate_nonblock - 设置I/O为阻塞模式
* @fd: 文件描符符
*/
int deactivate_nonblock(int fd)
{
int ret = 0;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
{
ret = flags;
printf("deactivate_nonblock() Err:%d", ret);
return ret;
}
flags &= ~O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -1)
{
printf("deactivate_nonblock() Err:%d", ret);
return ret;
}
return ret;
}
/**
* read_timeout - 读超时检测函数,不含读操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int read_timeout(int fd, int wait_seconds)
{
int ret = 0;
if (wait_seconds > 0)
{
fd_set read_fdset;
FD_ZERO(&read_fdset);
FD_SET(fd, &read_fdset);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
//select返回值三态
//1 若timeout时间到(超时),没有检测到读事件 ret返回=0
//2 若ret返回<0 && errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理)
//2-1 若返回-1,select出错
//3 若ret返回值>0 表示有read事件发生,返回事件发生的个数
do
{
ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret == 1)
ret = 0;
}
return ret;
}
/**
* write_timeout - 写超时检测函数,不含写操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int write_timeout(int fd, int wait_seconds)
{
int ret = 0;
if (wait_seconds > 0)
{
fd_set write_fdset;
FD_ZERO(&write_fdset);
FD_SET(fd, &write_fdset);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret == 1)
ret = 0;
}
return ret;
}
/**
* accept_timeout - 带超时的accept
* @fd: 套接字
* @addr: 输出参数,返回对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT,错误返回-1
*/
int accept_timeout(int fd, struct sockaddr_in *addr, int wait_seconds)
{
int ret;
if(wait_seconds > 0)
{
fd_set accept_fdset;
FD_ZERO(&accept_fdset);
FD_SET(fd, &accept_fdset);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == -1)
return -1;
else if (ret == 0)
{
errno = ETIMEDOUT;
return -1;
}
}
//一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立
//此时再调用accept将不会堵塞
socklen_t addrlen = sizeof(struct sockaddr_in);
if (addr != NULL)
ret = accept(fd, (struct sockaddr*)addr, &addrlen); //返回已连接套接字
else
ret = accept(fd, NULL, NULL);
if (ret < 0)
{
printf("accept() Err:%d \n", errno);
return ret;
}
return ret;
}
/**
* connect_timeout - connect连接超时
* @fd: 套接字
* @addr: 要连接的对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int connect_timeout(int fd, struct sockaddr_in *addr, int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
if (wait_seconds > 0)
{
ret = activate_nonblock(fd);
if(ret == -1)
return -1;
}
ret = connect(fd, (struct sockaddr*)addr, addrlen);
if (ret < 0 && errno == EINPROGRESS)
{
fd_set connect_fdset;
FD_ZERO(&connect_fdset);
FD_SET(fd, &connect_fdset);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
// 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中
ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == 0)
{
ret = -1;
errno = ETIMEDOUT;
return ret;
}
else if (ret < 0)
return -1;
else if (ret == 1)
{
/* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
/* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
int err;
socklen_t socklen = sizeof(err);
int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
if (sockoptret == -1)
{
return -1;
}
if (err == 0)
{
ret = 0;
}
else
{
errno = err;
ret = -1;
}
}
}
if (wait_seconds > 0)
{
int tmp;
tmp = deactivate_nonblock(fd);
if(tmp == -1)
return -1;
}
return ret;
}
//客户端环境初始化 设置结构体中的信息
/**
* cliet_init - 创建客户端环境初始化
* @handle: 句柄
* @conntime: 连接最长时长
* @sendtime: 发送最长时长
* @recvtime: 接受最长时长
* 成功返回 0,失败返回-1,
*/
int cliet_init(Handle **handle, int conntime, int sendtime, int recvtime)
{
int ret = 0;
if (handle == NULL ||conntime < 0 || sendtime < 0 || recvtime < 0)
{
printf("cliet_init() Parem Err! \n");
return -1;
}
Handle *tmp = (Handle *)malloc(sizeof(Handle));
if (tmp == NULL)
{
printf("cliet_init() malloc Err!\n");
return -1;
}
tmp->conntime = conntime;
tmp->sendtime = sendtime;
tmp->recvtime = recvtime;
*handle = tmp;
return ret;
}
/**
* cliet_getconn - 创建客户端连接
* @handle: 句柄
* @ip: ip地址
* @port: 端口
* @conn: 回传出去的连接
* 成功返回 0,失败返回-1, 超时返回-1并且errno = ETIMEDOUT
*/
int cliet_getconn(Handle *handle, char *ip, int port, int *connfd)
{
int ret = 0;
if (handle == NULL || ip==NULL || connfd==NULL || port<0 || port>65537)
{
printf("cliet_getconn() Param Err! \n");
return -1;
}
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
ret = errno;
printf("socket() Err!\n");
return ret;
}
struct sockaddr_in servaddr;
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = inet_addr(ip);
Handle *tmp = (Handle* )handle;
ret = connect_timeout(sockfd, (struct sockaddr_in*) (&servaddr), (unsigned int)tmp->conntime);
if (ret < 0)
return ret;
handle->sockfd = sockfd;
*connfd = sockfd;
return ret;
}
//客户端发送报文
/**
* client_send - 发送报文数据
* @handle: 句柄
* @sendbuf: 发送数据packet
* 成功返回成功发送数据的长度,失败返回-1 或者 < 发送数据的长度
*/
int client_send(Handle *handle, packet* sendbuf)
{
int ret = write_timeout(handle->sockfd, handle->sendtime);
if (ret < 0)
{
//失败返回-1,超时返回-1并且errno = ETIMEDOUT
if (ret == -1 && errno == ETIMEDOUT)
{
printf("client_send() write_timeout() Timeout Err!\n ");
return ret;
}
else
return ret;
}
int n = strlen(sendbuf->buf);
sendbuf->len = htonl(n);
writen(handle->sockfd, sendbuf, 4 + n);
}
int client_recv(Handle *handle, packet* recvbuf)
{
int ret = read_timeout(handle->sockfd, handle->recvtime);
if (ret < 0)
{
//失败返回-1,超时返回-1并且errno = ETIMEDOUT
if (ret == -1 && errno == ETIMEDOUT)
{
printf("client_rev() read_timeout() Timeout Err!\n ");
return ret;
}
else
return ret;
}
readn(handle->sockfd, &recvbuf->len, 4);
//将转换为本地字节存储
int n = ntohl(recvbuf->len);
return readn(handle->sockfd, &recvbuf->buf, n);
}
//服务器端初始化
/**
* server_init - 服务器初始化环境
* @port:服务器端口
* @listenfd:传出参数 返回监听套接字
* 成功返回0, 失败返回相应的错误码
*/
int server_init(int port, int *listenfd)
{
int ret = 0;
int listen_fd;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
{
ret = errno ;
printf("socket() Err: %d \n", ret);
return ret;
}
//设置地址复用
int on = 1;
ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
if (ret < 0)
{
ret = errno ;
printf("setsockopt() Err:%d \n", ret);
return ret;
}
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = inet_addr("172.20.53.162");
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_aton("127.0.0.1", &servaddr.sin_addr);
ret = bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (ret < 0)
{
ret = errno ;
printf("bind() Err:%d \n", ret);
return ret;
}
ret = listen(listen_fd, SOMAXCONN);
if (ret < 0)
{
ret = errno ;
printf("listen() Err:%d \n", ret);
return ret;
}
*listenfd = listen_fd;
return 0;
}
//服务器accept函数
/**
* server_accept - 服务器接受
* @listenfd: 监听套接字
* @connfd: 输出参数,返回连接套接字
* @timeout: 等待超时秒数,如果为0表示正常模式
* 成功返回0,失败返回对应的错误码
*/
int server_accept(int listenfd, int *connfd, struct sockaddr_in *addr, int timeout)
{
int ret = 0;
ret = accept_timeout(listenfd, addr, (unsigned int)timeout);
if (ret < 0)
{
if (ret == -1 && errno == ETIMEDOUT)
{
printf("accept_timeout() Err!\n");
return ret;
}
ret = errno;
return ret;
}
*connfd = ret;
return 0;
}
//服务器端端接受报文
/**
* server_send - 服务器发送报文
* @connfd: 连接套接字
* @recvbuf: 接受数据packet
* @timeout: 等待超时秒数,如果为0表示正常模式
* 成功返回读取数据的长度,失败-1, 超时返回-1错误码为ETIMEOUT
*/
int server_recv(int connfd, packet* recvbuf, int timeout)
{
int ret = read_timeout(connfd, timeout);
if (ret < 0)
{
if (ret == -1 && errno == ETIMEDOUT)//失败返回-1,超时返回-1并且errno = ETIMEDOUT
{
printf("server_recv() read_timeout() Timeout Err!\n ");
return ret;
}
else
return ret;
}
//读取包的头4字节
ret = readn(connfd, recvbuf, 4); //读包头 4个字节
if (ret == -1)
{
printf("readn error\n");
return -1;
}//如果读取的个数小于4,则客服端已经关闭
else if (ret < 4)
{
printf("client close\n");
return -1;
}
int n = ntohl(recvbuf->len);
return readn(connfd, &(recvbuf->buf), n); //根据长度读数据
}
//服务器端发送报文
/**
* server_send - 服务器发送报文
* @connfd: 连接套接字
* @sendbuf: 发送数据packet
* @timeout: 等待超时秒数,如果为0表示正常模式
* 成功返回0,失败返回对应的错误码
*/
int server_send(int connfd, packet* sendbuf, int timeout)
{
int ret = write_timeout(connfd, timeout);
if (ret < 0)
{
if (ret == -1 && errno == ETIMEDOUT)//失败返回-1,超时返回-1并且errno = ETIMEDOUT
{
printf("server_send_() write_timeout() Timeout Err!\n ");
return ret;
}
else
return ret;
}
int n = ntohl(sendbuf->len);
//将接受到的数据再直接发出去。也就是所谓的echo服务器模型
return writen(connfd, sendbuf, 4 + n); //注意写数据的时候,多加4个字节
}
// echoclient.c
#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>
#include <fcntl.h>
#include "commsocket.h"
int echo_cli(int sockfd, Handle *handle)
{
packet sendbuf;
packet recvbuf;
bzero(&sendbuf, sizeof(sendbuf));
bzero(&recvbuf, sizeof(recvbuf));
while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
{
int ret = client_send(handle, &sendbuf);
client_recv(handle, &recvbuf);
fputs(recvbuf.buf, stdout);
bzero(&sendbuf, sizeof(sendbuf));
bzero(&recvbuf, sizeof(recvbuf));
}
close(sockfd);
}
int main(void)
{
// 作为传出参数
int sockfd;
//保存信息的句柄
Handle *handle = NULL;
//设置相应的超时信息。
int conntime = 15;
int sendtime = 5;
int recvtime = 5;
int ret;
ret = cliet_init(&handle, conntime, sendtime, recvtime);
ret = cliet_getconn(handle, "172.20.53.162", 8001, &sockfd);
if(ret!=0)
{
printf("cliet_getconn() Err!\n");
return ret;
}
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if (getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < 0)
ERR_EXIT("getsockname");
printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
echo_cli(sockfd, handle);
close(sockfd);
return 0;
}
// echoserver.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "commsocket.h"
int echo_srv(int connfd)
{
//定义了自定封包结构体
packet recvbuf;
bzero(&recvbuf, sizeof(recvbuf));
int ret;
while (1)
{
server_recv(connfd, &recvbuf, 20);
fputs(recvbuf.buf, stdout);
server_send(connfd, &recvbuf, 20);
bzero(&recvbuf, sizeof(recvbuf));
}
}
void handle_sigchld(int sig)
{
int pid = 0;
while ( ( pid = waitpid(-1, NULL, WNOHANG)) >0 )
{
printf("子进程退出,父进程要收尸: %d \n", pid);
}
}
int main(void)
{
//注册信号处理函数
signal(SIGCHLD, handle_sigchld);
int port = 8001;
//设置监听的事件,过了监听的时间就自动退出了。
int listenfd = 0;
//服务器环境初始化
int ret = server_init(port, &listenfd);
if (ret != 0)
{
printf("server_init() Err : %d \n", ret);
return ret;
}
//接下来尝试进行连接
while (1)
{
struct sockaddr_in peeraddr;
int connfd;
int timeout = 0;
//服务器accept函数,可以接受等待时间
ret = server_accept(listenfd, &connfd, &peeraddr, timeout);
if (ret != 0)
{
printf("server_accept() Err!\n");
return ret;
}
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid_t pid = fork();
if (pid == -1)
ERR_EXIT("fork");
if (pid == 0)
{
close(listenfd);
echo_srv(connfd);
close(connfd);
exit(EXIT_SUCCESS);
}
else
{
close(connfd);
}
}
return 0;
}