socket select 超時設定/select 集合
阿新 • • 發佈:2019-01-08
- server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "tcp.h"
void* work(void* arg)
{
int sckcli = *(int*) arg;
char buff[1024];
int ret;
ret = Readn( sckcli, buff, 1024, 3, 0);
if(ret < 0){
printf("read error\n");
close(sckcli);
return NULL;
}else{
buff[ret]='\0';
printf("received:%s\n", buff);
}
ret = Writen(sckcli, buff, ret, 3 , 0);
if(ret < 0){
printf("write error\n");
}
close(sckcli);
}
void* accpet_work(void* arg)
{
int sckcli;
int sckser = *(int *)arg;
pthread_t tid;
pthread_attr_t attr;
fd_set fd;
int ret;
while(1)
{
ret = select_fd_timeout_r(sckser, 3 , 0);
if(ret <= 0){
printf("1AcceptRemote ret socket Accept錯誤errno = [%d] %d\n", errno, ret);
continue;
}
sckcli = AcceptRemote(sckser);
if(INVALID_SOCKET == sckcli)
{
printf("2AcceptRemote ret socket Accept錯誤errno = [%d]\n", errno);
usleep(1);
continue;
}
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, work, &sckcli);
}
}
/*
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);
*/
int main(int rgvs, char **rgva)
{
int ret;
int len;
int sckser1;
int sckser2;
int i=0;
char buff[1024];
if(rgvs <3){
printf("%s <listen port port>\n", rgva[0]);
return 0;
}
pthread_t tid1;
pthread_t tid2;
pthread_attr_t attr;
sckser1 = ListenRemote(atoi(rgva[1]));
if(sckser1 >0){
setOpetinNoBlock(sckser1);
pthread_create(&tid1, NULL, accpet_work, &sckser1);
}
sckser2 = ListenRemote(atoi(rgva[2]));
if(sckser2 > 0){
setOpetinNoBlock(sckser2);
pthread_create(&tid2, NULL, accpet_work, &sckser2);
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(sckser1);
close(sckser2);
}
- tcp.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/tcp.h>
#include <time.h>
#include "tcp.h"
/*
FD_ZERO(fd_set *fdset) 將指定的檔案描述符集清空,在對檔案描述符集合進行設定前,必須對其進行初始化,如果不清空,由於在系統分配記憶體空間後,通常並不作清空處理,所以結果是不可知的。
FD_SET(fd_set *fdset) 用於在檔案描述符集合中增加一個新的檔案描述符。
FD_CLR(fd_set *fdset) 用於在檔案描述符集合中刪除一個檔案描述符。
FD_ISSET(int fd,fd_set *fdset) 用於測試指定的檔案描述符是否在該集合中
*/
int select_fd_init(fd_set *fdset)
{
FD_ZERO(fdset);
return 0;
}
int select_fd_del(fd_set *fdset, int fd)
{
FD_CLR(fd, fdset);
return 0;
}
int select_fd_add(fd_set *fdset, int fd)
{
FD_SET(fd, fdset);
return 0;
}
//在集合中返回<0出錯,>0可讀寫,==0超時
int select_fd_check_r(fd_set *fdset_r, int fd, int sec, int usec)
{
/*
返回值:返回狀態發生變化的描述符總數。
負值:select錯誤
正值:某些檔案可讀寫或出錯
0:等待超時,沒有可讀寫或錯誤的檔案
*/
struct timeval ptv;
int ret;
ptv.tv_sec = sec;
ptv.tv_usec = usec;
ret = select(fd + 1, fdset_r, NULL, NULL, &ptv);
if(ret < 0){
return -1;
}else if(ret > 0){
return 1;
}
return 0;
}
//在集合中返回<0出錯,>0可讀寫,==0超時
int select_fd_check_w(fd_set *fdset_w, int fd, int sec, int usec)
{
/*
返回值:返回狀態發生變化的描述符總數。
負值:select錯誤
正值:某些檔案可讀寫或出錯
0:等待超時,沒有可讀寫或錯誤的檔案
*/
struct timeval ptv;
int ret;
ptv.tv_sec = sec;
ptv.tv_usec = usec;
ret = select(fd + 1, NULL, fdset_w, NULL, &ptv);
if(ret < 0){
return -1;
}else if(ret > 0){
return 1;
}
return 0;
}
//在集合中返回<0出錯,>0可讀寫,==0超時
int select_fd_check_rw(fd_set *fdset_r, fd_set *fdset_w, int fd, int sec, int usec)
{
/*
返回值:返回狀態發生變化的描述符總數。
負值:select錯誤
正值:某些檔案可讀寫或出錯
0:等待超時,沒有可讀寫或錯誤的檔案
*/
struct timeval ptv;
int ret;
ptv.tv_sec = sec;
ptv.tv_usec = usec;
ret = select(fd + 1, fdset_r, fdset_w, NULL, &ptv);
if(ret < 0){
return -1;
}else if(ret > 0){
return 1;
}
return 0;
}
//當描述符fd在描述符集fdset中返回非零值,否則,返回零。
int select_fd_check2(fd_set *fdset, int fd)
{
return FD_ISSET(fd, fdset);
}
/*
返回值:返回狀態發生變化的描述符總數。
負值:select錯誤
正值:某些檔案可讀寫或出錯
0:等待超時,沒有可讀寫或錯誤的檔案
*/
int select_fd_timeout_r(int fd, int sec, int usec)
{
struct timeval ptv;
fd_set fdset;
int ret;
ptv.tv_sec = sec;
ptv.tv_usec = usec;
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
ret = select(fd + 1, &fdset, NULL, NULL, &ptv);
if(ret < 0){
return -1;
}else if(ret > 0){
return 1;
}
return 0;
}
int select_fd_timeout_w(int fd, int sec, int usec)
{
struct timeval ptv;
fd_set fdset;
int ret;
ptv.tv_sec = sec;
ptv.tv_usec = usec;
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
ret = select(fd + 1, NULL, &fdset, NULL, &ptv);
if(ret < 0){
return -1;
}else if(ret > 0){
return 1;
}
return 0;
}
int select_fd_timeout_rw(int fd, int sec, int usec)
{
struct timeval ptv;
fd_set fdset;
int ret;
ptv.tv_sec = sec;
ptv.tv_usec = usec;
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
ret = select(fd + 1, &fdset, &fdset, NULL, &ptv);
if(ret < 0){
return -1;
}else if(ret > 0){
return 1;
}
return 0;
}
/*
在阻塞模式下, send函式的過程是將應用程式請求傳送的資料拷貝到傳送快取中傳送就返回.但由於傳送快取的存在,
表現為:如果傳送快取大小比請求傳送的大小要大,那麼send函式立即返回,同時向網路中傳送資料;
否則,send會等待接收端對之前傳送資料的確認,以便騰出快取空間容納新的待發送資料,
再返回(接收端協議棧只要將資料收到接收快取中,就會確認,並不一定要等待應用程式呼叫recv),如果一直沒有空間能容納待發送的資料,則一直阻塞;
在非阻塞模式下,send函式的過程僅僅是將資料拷貝到協議棧的快取區而已,如果快取區可用空間不夠,則盡能力的拷貝,立即返回成功拷貝的大小;如快取區可用空間為0,則返回-1,同時設定errno為EAGAIN.
阻塞就是幹不完不準回來,非阻塞就是你先幹,我現看看有其他事沒有,完了告訴我一聲。
*/
int CloseSocket(int sockfd)
{
shutdown(sockfd, 2);
close(sockfd);
sockfd = INVALID_SOCKET;
return (0);
}
int Writen(int sckid, unsigned char *buf, int len, int sec, int usec)
{
struct timeval ptv = {0,0};
ptv.tv_sec = sec;
ptv.tv_usec = usec;
//設定傳送超時
setsockopt(sckid, SOL_SOCKET,SO_SNDTIMEO, (char *)&ptv,sizeof(struct timeval));
if (send(sckid, buf, len, 0) != len)
{
return (-1);
}
return (len);
}
int Readn(int sckid, char *buf, int len, int sec, int usec)
{
int rc = 0;
struct timeval ptv = {0,0};
ptv.tv_sec = sec;
ptv.tv_usec = usec;
//設定接收超時
setsockopt(sckid, SOL_SOCKET,SO_RCVTIMEO, (char *)&ptv,sizeof(struct timeval));
if ((rc = recv(sckid, buf, len, 0)) <= 0)
{
return (-1);
}
return (rc);
}
int ConnectRemote(char *ip, int port, int sec, int usec)
{
struct sockaddr_in psckadd;
struct linger Linger;
int sckcli;
int on = 1;
struct timeval ptv;
memset((char *)(&psckadd), '0', sizeof(struct sockaddr_in));
psckadd.sin_family = AF_INET;
psckadd.sin_addr.s_addr = inet_addr(ip);
psckadd.sin_port = htons((u_short) port);
if ((sckcli = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
return (INVALID_SOCKET);
}
//設定連線超時時間
ptv.tv_sec = sec;
ptv.tv_usec = usec;
if (setsockopt(sckcli, SOL_SOCKET, SO_SNDTIMEO, &ptv, sizeof(ptv)))
{
close(sckcli);
return (INVALID_SOCKET);
}
if (connect(sckcli, (struct sockaddr *)(&psckadd), sizeof(struct sockaddr_in)) < 0)
{
close(sckcli);
return (INVALID_SOCKET);
}
//設定接收tcp外帶資料
if (setsockopt(sckcli, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)))
{
CloseSocket(sckcli);
return (INVALID_SOCKET);
}
on = 1;
setsockopt(sckcli, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
/*
TCP_NODELAY和TCP_CORK都是禁用Nagle演算法,只不過NODELAY完全關閉演算法,立即傳送而TCP_CORK完全由自己決定傳送時機
*/
setsockopt(sckcli, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
return (sckcli);
}
int ListenRemote(int port)
{
int yes=1;
int server_socket = INVALID_SOCKET;
struct sockaddr_in server_addr;
//建立套接字
if((server_socket=socket(AF_INET,SOCK_STREAM,0))<0){
return INVALID_SOCKET;
}
//設定為可重複使用
if(setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int))==-1){
close(server_socket);
return INVALID_SOCKET;
}
//設定伺服器地址資訊設定
server_addr.sin_family=AF_INET; //TCP
server_addr.sin_port=htons(port);
server_addr.sin_addr.s_addr=INADDR_ANY; //本地IP地址
memset(server_addr.sin_zero, 0,sizeof(server_addr.sin_zero));
//繫結套接字與地址資訊
if(bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1){
close(server_socket);
return INVALID_SOCKET;
}
//偵聽
if(listen(server_socket,5)==-1){
close(server_socket);
return INVALID_SOCKET;
}
return server_socket;
}
int AcceptRemote(int sockfd)
{
int client_sockfd = INVALID_SOCKET;//客戶端套接字
struct sockaddr_in remote_addr; //客戶端網路地址結構體
int sin_size=sizeof(struct sockaddr_in);
/*等待客戶端連線請求到達*/
if((client_sockfd=accept(sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
{
return INVALID_SOCKET;
}
//預設為阻塞模式
return client_sockfd;
}
void setOpetinNoBlock(int fd)
{
//設定為非阻塞模式
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
/*
int socket_recv(int sockfd, char* buffer, int buflen)
{
int rs=1;
int ret;
while(rs)
{
ret = recv(activeevents[i].data.fd, buffer, buflen, 0);
if(ret < 0)
{
// 由於是非阻塞的模式,所以當errno為EAGAIN時,表示當前緩衝區已無資料可讀
// 在這裡就當作是該次事件已處理處.
if(errno == EAGAIN)
break;
}
else if(ret == 0)
{
// 這裡表示對端的socket已正常關閉.
break;
}
if(buflen == ret)
rs = 1; // 需要再次讀取
else
rs = 0;
}
return ret;
}
int socket_send(int sockfd, const char* buffer, int buflen)
{
int tmp;
int total = buflen;
const char *p = buffer;
while(1)
{
tmp = send(sockfd, p, total, 0);
if(tmp < 0)
{
// 當send收到訊號時,可以繼續寫,但這裡返回-1.
if(errno == EINTR)
return -1;
// 當socket是非阻塞時,如返回此錯誤,表示寫緩衝佇列已滿,
// 在這裡做延時後再重試.
if(errno == EAGAIN)
{
usleep(1000);
continue;
}
return -1;
}
if((size_t)tmp == total)
return buflen;
total -= tmp;
p += tmp;
}
return tmp;
}
*/
- tcp.h
#define INVALID_SOCKET -1
int select_fd_init(fd_set *fdset);
int select_fd_del(fd_set *fdset, int fd);
int select_fd_add(fd_set *fdset, int fd);
int select_fd_check_r(fd_set *fdset_r, int fd, int sec, int usec);
int select_fd_check_w(fd_set *fdset_w, int fd, int sec, int usec);
int select_fd_check_rw(fd_set *fdset_r, fd_set *fdset_w,int fd, int sec, int usec);
int select_fd_check2(fd_set *fdset, int fd);
int select_fd_timeout_r(int fd, int sec, int usec);
int select_fd_timeout_w(int fd, int sec, int usec);
int select_fd_timeout_rw(int fd, int sec, int usec);
int CloseSocket(int sockfd);
int Writen(int sckid, unsigned char *buf, int len, int sec, int usec);
int Readn(int sckid, char *buf, int len, int sec, int usec);
int ConnectRemote(char *ip, int port, int sec, int usec);
int ListenRemote(int port);
int AcceptRemote(int sockfd);
void setOpetinNoBlock(int fd);