顯示具有 socket 標籤的文章。 顯示所有文章
顯示具有 socket 標籤的文章。 顯示所有文章

2008年8月14日 星期四

An event notification library - libevent

libevent
The libevent API provides a mechanism to execute a callback function when a specific event occurs on a file descriptor or after a timeout has been reached. Furthermore, libevent also support callbacks due to signals or regular timeouts.
libevent is meant to replace the event loop found in event driven network servers. An application just needs to call event_dispatch() and then add or remove events dynamically without having to change the event loop.
簡單的講就是可以變成事件驅動的方式寫程式

1. 從官網下載libevent的tarball後
2. ./configure && make && make install
3. echo "/usr/local/lib" >> /etc/ld.so.conf
4. ldconfig
5. 下載下面那個範例程式就可以試玩囉!

然後可以下載http://www.zhongguowen.com/demo/fhttpd/fhttpd.tar.gz
基於libevent的http server程式碼如下:

/**
* http server example, supported by libevent
*
* @autor lowellzhong<at>gmail<dot>com
* @website http://www.zhongguowen.com/
*/
#include <stdio.h>
#include <event.h>
#include <evhttp.h>


void fhttpd_gencb(struct evhttp_request * evreq, void * arg);


int main(int argc, char** argv)
{
struct event_base *evbase = NULL;
struct evhttp *evhttp = NULL;
unsigned short port = 8080;
const char *host = "0.0.0.0";


/* 1). event initialization */
evbase = event_init();
if(NULL == evbase)
{
perror("event_base_new");
return 1;
}


/* 2). event http initialization */
evhttp = evhttp_new(evbase);
if(NULL == evhttp)
{
perror("evhttp_new");
return 2;
}


/* 3). set general callback of http request */
evhttp_set_gencb(evhttp, &fhttpd_gencb, NULL);


/* 4). bind socket */
if(0 != evhttp_bind_socket(evhttp, host, port))
{
perror("evhttp_bind_socket");
return 3;
}
fprintf(stderr, "http server is running (%s:%d)\n", host, port);


/* 5). start http server */
if(0 == event_base_dispatch(evbase))
{
perror("event_base_dispatch");
return 4;
}


/* 6). free resource before exit */
evhttp_free(evhttp);
event_base_free(evbase);


return 0;
}


void fhttpd_gencb(struct evhttp_request * evreq, void * arg)
{
struct evbuffer *evbuff = NULL;


fprintf(stderr, "[%s] %s\n", evreq->remote_host, evreq->uri);


/* create a evbuffer variable for response */
evbuff = evbuffer_new();
if(NULL == evbuff)
{
perror("evbuffer_new");
return;
}


/* store response html in evbuffer */
evbuffer_add_printf(
evbuff,
"<html>\n<meta http-equiv=\"Content-Type\" content=\"text/html;"
"charset=GB2312\" />\n<head>\n<title>Welcome %s</title>\n</head>\n<body>\n"
"<h1>Welcome %s</h1>\n<p>your ip:%s, port %d</p>\n"
"<p>request uri: %s</p>\n<p><a href=\"<A href='http://wwww.zhongguowen.com\">www.zhongguowen.com</a></p></body>\n</html>",
evreq->remote_host, evreq->remote_host, evreq->remote_host,
evreq->remote_port, evreq->uri
);


/* change response charset, 'text/html; charset=ISO-8859-1' by default */
evhttp_add_header(evreq->output_headers, "Content-Type", "text/html; charset=GB2312");

/* send response html to client */
evhttp_send_reply(evreq, HTTP_OK, "", evbuff);


/* don't forget to release evbuffer */
evbuffer_free(evbuff);
}

browser連接上的執行結果

但也有人說libevent不好,要改用libev@@
libev官網如下:http://software.schmorp.de/pkg/libev.html

資料及參考來源:
libevent
http://www.monkey.org/~provos/libevent/
Gea-Suan Lin's Blog
http://blog.gslin.org/archives/2005/11/24/220/
羅威爾的博客 - 基於libevent的http server範例
http://www.zhongguowen.com/blog/?p=142
有簡易版DNS及http server的程式碼
http://unx.ca/log/2006/10/17/new-libevent-dns-and-http-support/

2008年8月13日 星期三

Socket Descriptors



這是一張Socket Descriptors的概念圖

什麼是Zero-Copy?

什麼是Zero-Copy?
想要了解這個名詞是指什麼意思的話,讓我們從一個簡單的範例開始,這個範例是server讀取一個檔案,然後把檔案資料經由socket將資料傳送給client。
簡化範例程式如下:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

Figure 1的圖說明了,這兩行程式實際的運作流程。(1)當執行read函式後,進入Kernel的syscall read(),檔案資料會經由DMA傳到Kernel管的Buffer,然後再由CPU將檔案資料搬到user buffer(tmp_buf)裏。
(2)執行write後,Kernel的syscall write(),再用CPU去把user buffer的資料搬到socket buffer裏,資料進到socket buffer後,會再經由DMA的方式將資料送出去給client。

問題分析:
從整個流程你應該會發現有一堆資料是重覆的,如果能把這些部份改掉,那就可以減少記憶體的消耗並增加效能。
以硬體的角度來看,其實是可以做到直接跳過記憶體的資料暫存的,直接把檔案資料傳到網路去,這樣子的功能是最直接最有效率的,但並不是所有的硬體都支援這種方式。

那我們是否可以減少user buffer這個部份呢?答案是肯定的,我們必需使用mmap來取代read的功能。
簡化範例程式如下:

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

Figure 2的圖說明了,這兩行程式實際的運作流程。(1)mmap執行後,如同read system call會將檔案資料經由DMA複製一份到kernel buffer,但不同的地方是,read()會需要把kernel buffer複製到user buffer,mmap()並不會,mmap的user buffer跟kernel buffer是同一個位置,所以mmap可以減少一次CPU copy。
(2)write()執行,把kernel buffer經由CPU複製到socket buffer,然後再經由DMA複製到client去。

問題分析:
但是使用mmap來改善並不是不需要付出代價的,當你使用mmap+write的方法時,假設同時又有另外一支程式對同一個檔案執行write時,將會引發SIGBUS的訊號,因為你執行了一個錯誤的記憶體存取,而它的預設處理行為是,系統砍掉你的程式,並且產生core dump。
當然一支網路程式不應該這麼做處理的,有兩個方式來處理這個情況:
(1)方法一:在SIGBUS訊號設置callback function,當SIGBUS出現時由這個新設置的handler來進行處理,但這種方式不好,因為它是事後去補救,並不是正規的解決方法。
(2)方法二:使用租約(lease)的方式(windows裏稱opportunistic locking機會鎖)
如:
if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
perror("kernel lease set signal");
return -1;
}
/* l_type can be F_RDLCK F_WRLCK */
if(fcntl(fd, F_SETLEASE, l_type)){
perror("kernel lease set type");
return -1;
}

更好的做法是使用sendfile函式
簡化範例程式如下:
sendfile(socket, file, len);
Figure 3的圖說明了,這行程式在Kernel 2.1版本的實際運作流程,sendfile直接取代了read/write兩個函式,並且減少了context switch的次數。(1)sendfile執行後,檔案資料會經由DMA傳給Kernel buffer,再由CPU複製到socket buffer去
(2)再把socket buffer的資料經由DMA傳給client去,所以執行了2次DMA Copy及1次的CPU Copy,總共3次的資料複製。

問題分析:
所以到目前為止我們已看到改善了不少地方了,但還是有一份重複的資料,那就是socket buffer,這份資料是否也可以不要呢?基本上也是可行的,只要硬體提供一點點幫助是可以做到的,那就是gather(聚合)的功能,這個功能主要的目的是,待發送端不要求存放的資料位址是連續的記憶體空間,可以是分散在記憶體的各個位置。所以到了2.4的kernel以後,socket buffer的descriptor做了一些變動,以支援gather的需求,而這個功能就是Zero-Copy。
這種方式不僅僅是減少了context switch而且也減少了buffer的使用,從上層的程式來講,也不需要做任何的變動。所以程式同樣的還是底下這行
sendfile(socket, file, len);
Figure 4的圖說明了,這行程式在Kernel 2.4版本的實際運作流程(1)sendfile執行後,檔案資料經由DMA傳給Kernel buffer,但已不會再把資料copy到socket buffer了,socket buffer只會去管有那些Kernel buffer的address及資料長度,所以圖是用apend。
(2)資料傳給client去也是用DMA的方式,但來源變成kernel buffer了。

所以就完成了,不需要CPU去搬資料,而是純DMA搬資料的Zero-Copy了。

原本資料來源:
http://www.linuxjournal.com/article/6345

測試文章:有人針對這篇文章去進行測試的實驗結果
雖然它測起來的傳輸速度似乎沒差,但應再加上CPU負載的資料及記憶體使用量去分析,如果速度一樣但CPU loading變輕,client端很多但記憶體使用量減少,那還是有很高的實用價值。
但還有個問題,網路傳輸不見得只有直接傳檔案啊,如果是傳非檔案的資料,sendfile還是適用嗎?
http://bbs.lpi-china.org/viewthread.php?tid=4292&extra=page%3D1

實現SIGIO驅動的socket範例

此程式由realtang所撰寫的,實現非同步的UDP Socket做法。
在接收到資料及檢測到異常時,引發SIGIO訊號。
SIGIO訊號是非同步傳輸的專用訊號。

Server端程式碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h> /*socket address struct*/
#include <arpa/inet.h> /*host to network convertion*/
#include <sys/socket.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/ioctl.h>
#define MAX_TRANSPORT_LENTH 512

static int g_var = 0;
static int g_skt = 0;
void sig_handler(int signum)
{
char  buf[MAX_TRANSPORT_LENTH+1] = "";
int len = 0;
len = read(g_skt,buf,MAX_TRANSPORT_LENTH);
if (len<0)
{
perror("Read socket failed");
exit(-1);
}
else
{
printf("In SIGIO handler,got msg:%s\n",buf);
}
}

int main()
{
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family =  AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(50001);

signal(SIGIO,sig_handler);

g_skt = socket(AF_INET,SOCK_DGRAM,0);
if(g_skt == -1)
{
perror("Create socket failed");
exit(-1);
}

int len = sizeof(addr);
int ret = 0;

int on = 1;
    ret = fcntl(g_skt, F_SETOWN, getpid());//Set process or process group ID to receive SIGIO signals
if(-1 == ret)
{
perror("Fcntl F_SETOWN failed");
exit(-1);
}
    ret = ioctl(g_skt, FIOASYNC, &on);
if(-1 == ret)   
{
perror("Fcntl FIOASYNC failed");
exit(-1);
}   
    ret = ioctl(g_skt, FIONBIO, &on);
if(-1 == ret)   
{
perror("ioctl FIONBIO failed");
exit(-1);
}

ret = bind(g_skt,(struct sockaddr *)&addr,sizeof(addr));
if(-1 == ret)
{
perror("Bind socket failed");
exit(-1);
}
while(1)
{
printf("I am running\n");
sleep(2);
}
close(g_skt);
}

Client端程式碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>   /*socket address struct*/
#include <arpa/inet.h>   /*host to network convertion*/
#include <sys/socket.h>
#include <signal.h>
#define MAX_TRANSPORT_LENTH 512

int main()
{
 struct sockaddr_in addr;
 memset(&addr,0,sizeof(addr));
 addr.sin_family =  AF_INET;
 addr.sin_addr.s_addr = inet_addr("192.168.1.106");
 addr.sin_port = htons(50001);
 
 int sock;
 sock = socket(AF_INET,SOCK_DGRAM,0);
 if(sock == -1)
 {
  perror("Create socket failed");
  exit(-1);
 }
 
 int ret;
 ret = connect(sock,(struct sockaddr *)&addr,sizeof(addr));
 if(ret == -1)
 {
  perror("Connect socket failed");
  exit(-1);  
 }  
 while(1)
 {
  printf("Will send messge to server\n");
  write(sock,"Some unknown infomation\n",MAX_TRANSPORT_LENTH);
  sleep(1);
 }
 
}

daemon修改後的Server版本:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define MAX_LENTH 1500

static int nqueue = 0;
void sigio_handler(int signum)
{
if (signum = SIGIO)
nqueue++;
return;
}

static recv_buf[MAX_LENTH];
int main(int argc, char *argv[])
{
int sockfd, on = 1;
struct sigaction action;
sigset_t newmask, oldmask;
struct sockaddr_in addr;

memset(&addr, 0, sizeof(addr));
addr.sin_family =  AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(50001);
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("Create socket failed");
exit(-1);
}
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("Bind socket failed");
exit(-1);
}

memset(&action, 0, sizeof(action));
action.sa_handler = sigio_handler;
action.sa_flags = 0;
sigaction(SIGIO, &action, NULL);
if (fcntl(sockfd, F_SETOWN, getpid()) == -1) {
perror("Fcntl F_SETOWN failed");
exit(-1);
}
if (ioctl(sockfd, FIOASYNC, &on) == -1) {
perror("Ioctl FIOASYNC failed");
exit(-1);
}

        sigemptyset(&oldmask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGIO);
while (1) {
int len;
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
while (nqueue == 0)
sigsuspend(&oldmask);
len = recv(sockfd, recv_buf, MAX_LENTH, MSG_DONTWAIT);
if (len == -1 && errno == EAGAIN)
nqueue = 0;
sigprocmask(SIG_SETMASK, &oldmask, NULL);
if (len >= 0)
printf("recv %d byte(s)\n", len);

}
}

資料來源:
http://www.linuxsir.org/bbs/showthread.php?t=214611

Socket 基本多工方式介紹

Socket 多工輸入/輸出 - select() 功能呼叫
Socket 多工連線 - fork() 功能呼叫
Socket 多工連線 - fork() 流程圖
Socket 多工範例 - xinetd
Socket 多工範例 - xinetd 流程圖

資料來源:
http://140.127.138.46/tsnien/Teach_Manu/F7858/F7858_HTML/chap10/chap10-m.htm

2008年8月12日 星期二

UDP Server/Client 範例程式

好像不少人會找這個Sample Code, 小修改一下好了. 先前的Code有不少的Warning出現而且會Crash耶!
底下分別列出UDP Server及Client的範例程式.

UDP Server(udp-server.c) 利用 socket 介面設計網路應用程式程式啟動後等待 client 端連線,連線後印出對方之 IP 位址並顯示對方所傳遞之訊息,並回送給 Client 端。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/errno.h>
#include <string.h>
#include <arpa/inet.h>

#define SERV_PORT 5134

#define MAXNAME 1024

extern int errno;

main(){
        int socket_fd;   /* file description into transport */
        int recfd; /* file descriptor to accept        */
        int length; /* length of address structure      */
        int nbytes; /* the number of read **/
        char buf[BUFSIZ];
        struct sockaddr_in myaddr; /* address of this service */
        struct sockaddr_in client_addr; /* address of client    */
        /*
         *      Get a socket into UDP/IP
         */
        if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) <0) {
                perror ("socket failed");
                exit(EXIT_FAILURE);
        }
        /*
         *    Set up our address
         */
        bzero ((char *)&myaddr, sizeof(myaddr));
        myaddr.sin_family = AF_INET;
        myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        myaddr.sin_port = htons(SERV_PORT);

        /*
         *     Bind to the address to which the service will be offered
         */
        if (bind(socket_fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) <0) {
                perror ("bind failed\n");
                exit(1);
        }

        /*
         * Loop continuously, waiting for datagrams
         * and response a message
         */
        length = sizeof(client_addr);
        printf("Server is ready to receive !!\n");
        printf("Can strike Cntrl-c to stop Server >>\n");
        while (1) {
                if ((nbytes = recvfrom(socket_fd, &buf, MAXNAME, 0, (struct sockaddr*)&client_addr, (socklen_t *)&length)) <0) {
                        perror ("could not read datagram!!");
                        continue;
                }


                printf("Received data form %s : %d\n", inet_ntoa(client_addr.sin_addr), htons(client_addr.sin_port));
                printf("%s\n", buf);

                /* return to client */
                if (sendto(socket_fd, &buf, nbytes, 0, (struct sockaddr*)&client_addr, length) < 0) {
                        perror("Could not send datagram!!\n");
                        continue;
                }
                printf("Can Strike Crtl-c to stop Server >>\n");
        }
}
UDP  Client (udp-client.c) 本程式啟動後向 Server (udp server) 要求連線,並送出某檔案給 Server,再由 Server 收回該檔案並顯示出該檔案的內容
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <string.h>

#define SERV_PORT 5134
#define MAXDATA   1024

#define MAXNAME 1024
int main(int argc, char **argv){
        int fd;     /* fd into transport provider */
        int i;     /* loops through user name */
        int length;    /* length of message */
        int size;    /* the length of servaddr */
        int fdesc;    /* file description */
        int ndata;    /* the number of file data */
        char data[MAXDATA]; /* read data form file */
        char data1[MAXDATA];  /*server response a string */
        char buf[BUFSIZ];     /* holds message from server */
        struct hostent *hp;   /* holds IP address of server */
        struct sockaddr_in myaddr;   /* address that client uses */
        struct sockaddr_in servaddr; /* the server's full addr */

        /*
         * Check for proper usage.
         */
        if (argc < 3) {
                fprintf (stderr, "Usage: %s host_name(IP address) file_name\n", argv[0]);
                exit(2);
        }
        /*
         *  Get a socket into UDP
         */
        if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
                perror ("socket failed!");
                exit(1);
        }
        /*
         * Bind to an arbitrary return address.
         */
        bzero((char *)&myaddr, sizeof(myaddr));
        myaddr.sin_family = AF_INET;
        myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        myaddr.sin_port = htons(0);

        if (bind(fd, (struct sockaddr *)&myaddr,
                                sizeof(myaddr)) <0) {
                perror("bind failed!");
                exit(1);
        }
        /*
         * Fill in the server's UDP/IP address
         */

        bzero((char *)&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(SERV_PORT);
        hp = gethostbyname(argv[1]);

        if (hp == 0) {
                fprintf(stderr, "could not obtain address of %s\n", argv[2]);
                return (-1);
        }
        bcopy(hp->h_addr_list[0], (caddr_t)&servaddr.sin_addr,
                        hp->h_length);

        /**開起檔案讀取文字 **/
        fdesc = open(argv[2], O_RDONLY);
        if (fdesc == -1) {
                perror("open file error!");
                exit (1);
        }
        ndata = read (fdesc, data, MAXDATA);
        if (ndata < 0) {
                perror("read file error !");
                exit (1);
        }
        data[ndata + 1] = '\0';

        /* 發送資料給 Server */
        size = sizeof(servaddr);
        if (sendto(fd, data, ndata, 0, (struct sockaddr*)&servaddr, size) == -1) {
                perror("write to server error !");
                exit(1);
        }
        /** 由伺服器接收回應 **/
        if (recvfrom(fd, data1, MAXDATA, 0, (struct sockaddr*)&servaddr, &size) < 0) {
                perror ("read from server error !");
                exit (1);
        }
        /* 印出 server 回應 **/
        printf("%s\n", data1);

}
部份編譯時遇到的Warning問題
Warning 1:
warning: incompatible implicit declaration of built-in function ‘exit’ [enabled by default]
加入#include <stdlib.h>
Warning 2:
warning: incompatible implicit declaration of built-in function ‘bzero’ [enabled by default]
加入#include <string.h>
執行結果如下: udp server side:

udp client side:
資料來源: 底下我原本參考的地方似乎已經失連了.
http://140.127.138.46/tsnien/Teach_Manu/F7858/Aid/udpserver.c
http://140.127.138.46/tsnien/Teach_Manu/F7858/Aid/udpclient.c

TCP Server/Client 範例程式

Server的PortNumber隨便取一個大1204且不在/etc/services中出現的號碼即可
/***  TCP Server tcpserver.c
 *
 * 利用 socket 介面設計網路應用程式
 * 程式啟動後等待 client 端連線,連線後印出對方之 IP 位址
 * 並顯示對方所傳遞之訊息,並回送給 Client 端。
 *
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/errno.h>


#define SERV_PORT 5134


#define MAXNAME 1024


extern int errno;


main()
{
 int socket_fd;      /* file description into transport */
 int recfd;     /* file descriptor to accept        */
 int length;     /* length of address structure      */
 int nbytes;     /* the number of read **/
 char buf[BUFSIZ];
 struct sockaddr_in myaddr; /* address of this service */
 struct sockaddr_in client_addr; /* address of client    */
/*                             
 *      Get a socket into TCP/IP
 */
 if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) <0) {
  perror ("socket failed");
  exit(1);
 }
/*
 *    Set up our address
 */
 bzero ((char *)&myaddr, sizeof(myaddr));
 myaddr.sin_family = AF_INET;
 myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 myaddr.sin_port = htons(SERV_PORT);


/*
 *     Bind to the address to which the service will be offered
 */
 if (bind(socket_fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) <0) {
  perror ("bind failed");
  exit(1);
 }


/*
 * Set up the socket for listening, with a queue length of 5
 */
 if (listen(socket_fd, 20) <0) {
  perror ("listen failed");
  exit(1);
 }
/*
 * Loop continuously, waiting for connection requests
 * and performing the service
 */
 length = sizeof(client_addr);
 printf("Server is ready to receive !!\n");
 printf("Can strike Cntrl-c to stop Server >>\n");
 while (1) {
  if ((recfd = accept(socket_fd,
      (struct sockaddr_in *)&client_addr, &length)) <0) {
   perror ("could not accept call");
   exit(1);
         }


  if ((nbytes = read(recfd, &buf, BUFSIZ)) < 0) {
   perror("read of data error nbytes !");
   exit (1);
  }
   
  printf("Create socket #%d form %s : %d\n", recfd,
  inet_ntoa(client_addr.sin_addr), htons(client_addr.sin_port));
  printf("%s\n", &buf);
 
  /* return to client */
  if (write(recfd, &buf, nbytes) == -1) {
   perror ("write to client error");
   exit(1);
  }
  close(recfd);
  printf("Can Strike Crtl-c to stop Server >>\n");
 }
}

/*  TCP  Client program (tcpclient.c)
 *
 *  本程式啟動後向 Server (tcpserver) 要求連線,
 *  並送出某檔案給 Server,再由 Server 收回該檔案
 *  並顯示出該檔案的內容
 *
 */


#include <stdio.h>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>


#define SERV_PORT 5134
#define MAXDATA   1024


#define MAXNAME 1024
main(argc, argv)
int argc;
char **argv;
{
 int fd;      /* fd into transport provider */
 int i;      /* loops through user name */
 int length;     /* length of message */
 int fdesc;     /* file description */
 int ndata;     /* the number of file data */
 char data[MAXDATA]; /* read data form file */
 char data1[MAXDATA];  /*server response a string */
 char buf[BUFSIZ];     /* holds message from server */
 struct hostent *hp;   /* holds IP address of server */
 struct sockaddr_in myaddr;   /* address that client uses */
 struct sockaddr_in servaddr; /* the server's full addr */


 /*
  * Check for proper usage.
  */
 if (argc < 3) {
  fprintf (stderr,
   "Usage: %s host_name(IP address) file_name\n", argv[0]);
  exit(2);
 }
 /*
  *  Get a socket into TCP/IP
  */
 if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  perror ("socket failed!");
  exit(1);
 }
 /*
  * Bind to an arbitrary return address.
  */
 bzero((char *)&myaddr, sizeof(myaddr));
 myaddr.sin_family = AF_INET;
 myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 myaddr.sin_port = htons(0);


 if (bind(fd, (struct sockaddr *)&myaddr,
   sizeof(myaddr)) <0) {
  perror("bind failed!");
  exit(1);
 }
 /*
  * Fill in the server's address and the data.
  */


 bzero((char *)&servaddr, sizeof(servaddr));
 servaddr.sin_family = AF_INET;
 servaddr.sin_port = htons(SERV_PORT);


 hp = gethostbyname(argv[1]);
 if (hp == 0) {
  fprintf(stderr,
   "could not obtain address of %s\n", argv[2]);
  return (-1);
 }


 bcopy(hp->h_addr_list[0], (caddr_t)&servaddr.sin_addr,
  hp->h_length);
 /*
  * Connect to the server連線.
  */
 if (connect(fd, (struct sockaddr *)&servaddr,
    sizeof(servaddr)) < 0) {
  perror("connect failed!");
  exit(1);
 }
 /**開起檔案讀取文字 **/
 fdesc = open(argv[2], O_RDONLY);
 if (fdesc == -1) {
  perror("open file error!");
  exit (1);
 }
 ndata = read (fdesc, data, MAXDATA);
 if (ndata < 0) {
  perror("read file error !");
  exit (1);
 }
 data[ndata] = '\0';


 /* 發送資料給 Server */
 if (write(fd, data, ndata) == -1) {
  perror("write to server error !");
  exit(1);
 }
 /** 由伺服器接收回應 **/
 if (read(fd, data1, MAXDATA) == -1) {
  perror ("read from server error !");
  exit (1);
 }
 /* 印出 server 回應 **/
 printf("%s\n", data1);


 close (fd);
}



 

Makefileall:tcpserver tcpclient
tcpserver:tcpserver.c
gcc $^ -o $@
tcpclient:tcpclient.c
gcc $^ -o $@


底下是另一個TCP Client的範例
用C實作TCP socket連接/讀/寫,使用fcntl設定nonblocking以處理connect overtime情況,使用select來處理socket讀寫overtime。

/* 
 
 * on Unix: 
 
 *    cc -c connector.c 
 
 *    cc -o connector connector.o 
 
 * 
 
 * on Windows NT: 
 
 *    open connector.c in Visual Studio 
 
 *    press 'F7' to link -- a project to be created 
 
 *    add wsock32.lib to the link section under project setting 
 
 *    press 'F7' again 
 
 * 
 
 * running: 
 
 *    type 'connector' for usage 
 
 */ 
 
 
#include <stdio.h>  
 
#include <stdlib.h>  
 
#include <string.h>  
 
#include <stdarg.h>  
 
#include <errno.h>  
 
#include <fcntl.h>  
 
#ifdef WIN32  
 
#include <winsock2.h>  
 
#else  
 
#include <unistd.h>  
 
#include <sys/types.h>  
 
#include <sys/socket.h>  
 
#include <sys/ioctl.h>  
 
#include <netinet/in.h>  
 
#include <arpa/inet.h>  
 
#include <netdb.h>  
 
#endif  
 
 
#ifndef INADDR_NONE  
 
#define INADDR_NONE     0xffffffff  
 
#endif  
 
#define MAX_STRING_LEN  1024  
 
#define BUFSIZE  2048  
 
 
#ifndef WIN32  
 
#define SOCKET int  
 
#else  
 
#define errno WSAGetLastError()  
 
#define close(a) closesocket(a)  
 
#define write(a, b, c) send(a, b, c, 0)  
 
#define read(a, b, c) recv(a, b, c, 0)  
 
#endif  
 
 
char buf[BUFSIZE];  
 
 
static char i_host[MAX_STRING_LEN];  /* site name */ 
 
static char i_port[MAX_STRING_LEN];  /* port number */ 
 
 
void err_doit(int errnoflag, const char *fmt, va_list ap);  
 
void err_quit(const char *fmt, ...);  
 
int tcp_connect(const char *host, const unsigned short port);  
 
void print_usage();  
 
 
//xnet_select x defines  
 
#define READ_STATUS  0  
 
#define WRITE_STATUS 1  
 
#define EXCPT_STATUS 2  
 
 
/* 
 
s    - SOCKET 
 
sec  - timeout seconds 
 
usec - timeout microseconds 
 
x    - select status 
 
*/ 
 
SOCKET xnet_select(SOCKET s, int sec, int usec, short x)  
 
{  
 
 int st = errno;  
 
 struct timeval to;  
 
 fd_set fs;  
 
 to.tv_sec = sec;  
 
 to.tv_usec = usec;  
 
 FD_ZERO(&fs);  
 
 FD_SET(s, &fs);  
 
 switch(x){  
 
  case READ_STATUS:  
 
  st = select(s+1, &fs, 0, 0, &to);  
 
  break;  
 
  case WRITE_STATUS:  
 
  st = select(s+1, 0, &fs, 0, &to);  
 
  break;  
 
  case EXCPT_STATUS:  
 
  st = select(s+1, 0, 0, &fs, &to);  
 
  break;  
 
 }  
 
 return(st);  
 
}  
 
 
int tcp_connect(const char *host, const unsigned short port)  
 
{  
 
    unsigned long non_blocking = 1;  
 
    unsigned long blocking = 0;  
 
    int ret = 0;  
 
    char * transport = "tcp";  
 
    struct hostent      *phe;   /* pointer to host information entry    */ 
 
    struct protoent *ppe;       /* pointer to protocol information entry*/ 
 
    struct sockaddr_in sin;     /* an Internet endpoint address  */ 
 
    SOCKET s;                    /* socket descriptor and socket type    */ 
 
    int error;  
 
 
#ifdef WIN32  
 
    {  
 
        WORD wVersionRequested;  
 
        WSADATA wsaData;  
 
        int err;  
 
   
 
        wVersionRequested = MAKEWORD( 2, 0 );  
 
   
 
        err = WSAStartup( wVersionRequested, &wsaData );  
 
        if ( err != 0 ) {  
 
            /* Tell the user that we couldn't find a usable */ 
 
            /* WinSock DLL.                               */ 
 
            printf("can't initialize socket library\n");  
 
            exit(0);  
 
        }  
 
    }  
 
#endif      
 
      
 
    memset(&sin, 0, sizeof(sin));  
 
    sin.sin_family = AF_INET;  
 
      
 
    if ((sin.sin_port = htons(port)) == 0)  
 
        err_quit("invalid port \"%d\"\n", port);  
 
      
 
    /* Map host name to IP address, allowing for dotted decimal */ 
 
    if ( phe = gethostbyname(host) )  
 
        memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);  
 
    else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )  
 
        err_quit("can't get \"%s\" host entry\n", host);  
 
      
 
    /* Map transport protocol name to protocol number */ 
 
    if ( (ppe = getprotobyname(transport)) == 0)  
 
        err_quit("can't get \"%s\" protocol entry\n", transport);  
 
      
 
    /* Allocate a socket */ 
 
    s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);  
 
    if (s < 0)  
 
        err_quit("can't create socket: %s\n", strerror(errno));  
 
      
 
    /* Connect the socket with timeout */ 
 
#ifdef WIN32  
 
    ioctlsocket(s,FIONBIO,&non_blocking);  
 
#else  
 
    ioctl(s,FIONBIO,&non_blocking);  
 
#endif  
 
    //fcntl(s,F_SETFL, O_NONBLOCK);  
 
    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1){  
 
        struct timeval tv;   
 
        fd_set writefds;  
 
        // 设置连接超时时间  
 
        tv.tv_sec = 10; // 秒数  
 
        tv.tv_usec = 0; // 毫秒  
 
        FD_ZERO(&writefds);   
 
        FD_SET(s, &writefds);   
 
        if(select(s+1,NULL,&writefds,NULL,&tv) != 0){   
 
            if(FD_ISSET(s,&writefds)){  
 
                int len=sizeof(error);   
 
                //下面的一句一定要,主要针对防火墙   
 
                if(getsockopt(s, SOL_SOCKET, SO_ERROR,  (char *)&error, &len) < 0)  
 
                    goto error_ret;   
 
                if(error != 0)   
 
                    goto error_ret;   
 
            }  
 
            else 
 
                goto error_ret; //timeout or error happen   
 
        }  
 
        else goto error_ret; ;   
 
 
#ifdef WIN32  
 
        ioctlsocket(s,FIONBIO,&blocking);  
 
#else  
 
        ioctl(s,FIONBIO,&blocking);  
 
#endif  
 
 
    }  
 
    else{  
 
error_ret:  
 
        close(s);  
 
        err_quit("can't connect to %s:%d\n", host, port);  
 
    }  
 
    return s;  
 
}  
 
 
void err_doit(int errnoflag, const char *fmt, va_list ap)  
 
{  
 
    int errno_save;  
 
    char buf[MAX_STRING_LEN];  
 
 
    errno_save = errno;   
 
    vsprintf(buf, fmt, ap);  
 
    if (errnoflag)  
 
        sprintf(buf + strlen(buf), ": %s", strerror(errno_save));  
 
    strcat(buf, "\n");  
 
    fflush(stdout);  
 
    fputs(buf, stderr);  
 
    fflush(NULL);  
 
    return;  
 
}  
 
 
/* Print a message and terminate. */ 
 
void err_quit(const char *fmt, ...)  
 
{  
 
    va_list ap;  
 
    va_start(ap, fmt);  
 
    err_doit(0, fmt, ap);  
 
    va_end(ap);  
 
    exit(1);  
 
}  
 
 
#ifdef WIN32  
 
char *optarg;  
 
 
char getopt(int c, char *v[], char *opts)  
 
{  
 
    static int now = 1;  
 
    char *p;  
 
 
    if (now >= c) return EOF;  
 
 
    if (v[now][0] == '-' && (p = strchr(opts, v[now][1]))) {  
 
        optarg = v[now+1];  
 
        now +=2;  
 
        return *p;  
 
    }  
 
 
    return EOF;  
 
}  
 
 
#else  
 
extern char *optarg;  
 
#endif  
 
 
#define required(a) if (!a) { return -1; }  
 
 
int init(int argc, char *argv[])  
 
{  
 
    char c;  
 
    //int i,optlen;  
 
    //int slashcnt;  
 
 
    i_host[0]  =  '\0';  
 
    i_port[0]  =  '\0';  
 
 
    while ((c = getopt(argc, argv, "h:p:?")) != EOF) {  
 
        if (c == '?')  
 
            return -1;  
 
        switch (c) {   
 
        case 'h':  
 
            required(optarg);  
 
            strcpy(i_host, optarg);  
 
            break;  
 
        case 'p':  
 
            required(optarg);  
 
            strcpy(i_port, optarg);  
 
            break;  
 
        default:  
 
            return -1;  
 
        }  
 
    }  
 
 
    /*  
 
     * there is no default value for hostname, port number,  
 
     * password or uri 
 
     */ 
 
    if (i_host[0] == '\0' || i_port[0] == '\0')  
 
        return -1;  
 
 
    return 1;  
 
}  
 
 
void print_usage()  
 
{  
 
    char *usage[] =  
 
    {  
 
        "Usage:",  
 
        "    -h    host name",  
 
        "    -p    port",  
 
        "example:",  
 
        "    -h 127.0.0.1 -p 4001",  
 
    };     
 
    int i;  
 
 
    for (i = 0; i < sizeof(usage) / sizeof(char*); i++)  
 
        printf("%s\n", usage[i]);  
 
      
 
    return;  
 
}  
 
 
int main(int argc, char *argv[])  
 
{  
 
    SOCKET fd;  
 
    int n;  
 
 
    /* parse command line etc ... */ 
 
    if (init(argc, argv) < 0) {  
 
        print_usage();  
 
        exit(1);  
 
    }  
 
 
    buf[0] = '\0';  
 
 
    /* pack the info into the buffer */       
 
    strcpy(buf, "HelloWorld");  
 
 
    /* make connection to the server */ 
 
    fd = tcp_connect(i_host, (unsigned short)atoi(i_port));  
 
 
    if(xnet_select(fd, 0, 500, WRITE_STATUS)>0){  
 
        /* send off the message */ 
 
        write(fd, buf, strlen(buf));  
 
    }  
 
    else{  
 
        err_quit("Socket I/O Write Timeout %s:%s\n", i_host, i_port);  
 
    }  
 
 
    if(xnet_select(fd, 3, 0, READ_STATUS)>0){  
 
        /* display the server response */ 
 
        printf("Server response:\n");  
 
        n = read(fd, buf, BUFSIZE);  
 
        buf[n] = '\0';  
 
        printf("%s\n", buf);  
 
    }  
 
    else{  
 
        err_quit("Socket I/O Read Timeout %s:%s\n", i_host, i_port);  
 
    }  
 
    close(fd);  
 
 
#ifdef WIN32  
 
    WSACleanup();  
 
#endif  
 
 
    return 0;  
 
}

資料來源:
http://140.127.138.46/tsnien/Teach_Manu/F7858/Aid/tcpserver.c
http://140.127.138.46/tsnien/Teach_Manu/F7858/Aid/tcpclient.c
http://www.zeali.net/entry/13

2008年8月8日 星期五

TCP/UDP Basic Flow

這個圖是TCP/IP基本的Server/Client流程這個圖是UDP基本的Server/Client流程TCP與UDP的程式差異:
  1. socket()參數不同

  2. UDP Server不需要使用listen及accept

  3. UDP收發資料用sendto/recvfrom

  4. TCP:address message在connect/accept時決定
    UDP:在每次的sendto/recvfrom函式中取得address message

  5. UDP:shutdown無效

SOCK_STREAM:使用TCP/IP協定。
SOCK_DGRAM:使用UDP協定。
SOCK_RAW:允許對更底層的協定,如IP,ICMP。
資料來源:
http://software.nju.edu.cn/jjsong/course-linux/lectures/Ch5%20Linux%20Network%20Programming.ppt
http://www-users.itlabs.umn.edu/classes/Spring-2006/csci4211/notes/socket.ppt

2008年8月7日 星期四

SOCKET listen() 函式

Listen函式

#include <sys/socket.h>

int listen(int socketfd,int backlog);

函式說明:listen()用來通知OS/network socketfdsocket已經可以接受建立連線。參數backlog指定同時能處理的最大連接要求,如果連接數目達此上限則client端將收到ECONNREFUSED的錯誤。Listen()未開始接收連線,只是設置socketlisten模式,真正接收client端連線的是accept()。通常listen()會在socket()bind()之後調用,接着才調用accept()。listen()並不會block住等待client的request。

回傳值

成功傳回0,錯誤傳回-1,再由errno判斷錯誤情況

errno

EBADF:參數sockfd非合法socket處理代碼

EACCESS:權限不足

EOPNOTSUPP指定的socket未支援listen模式。

參數:

參數1socketfdsocket()回傳之socket descriptor

參數2backlog還未完成連線請求的最大數量。

listen()只適用SOCK_STREAMSOCK_SEQPACKETsocket類型。如果socketAF_INET則參數backlog最大值可設至128


Linux系統可限制在佇列裏的數量在未真正完成連接前,可接受的等待連接要求的數量。內核要維護兩個佇列:完成連接佇列和未完成連接佇列。未完成佇列中存放的是TCP連接的三路握手未完成的連接,accept函數是從以連接佇列中取連接返回給進程;當以連接佇列為空時,進程將進入睡眠狀態。


但是man listen裏有句話
NOTES
The behaviour of the backlog parameter on TCP sockets changed with
Linux 2.2. Now it specifies the queue length for completely estab-
lished sockets waiting to be accepted, instead of the number of incom-
plete connection requests. The maximum length of the queue for incom-
plete sockets can be set using the tcp_max_syn_backlog sysctl. When
syncookies are enabled there is no logical maximum length and this
sysctl setting is ignored. See tcp(7) for more information.

  1. Synchronous:表示有一個thread處於blocked狀態專門負責等待網絡事件的到來 (including blocking,select, poll, WSAAsyncSelect, WSAEventSelect, Overlapped with Event Notification, Completion Port); WSAAsyncSelect 其實比較特殊,用户本身不用專門創建一個thread來等待,但是Windows窗口本身有一個消息循環,在等待網絡事件的時候,這個消息循環實際上幫助實現了 blocked thread的工作,所以還是把它放在這個類別下。

  2. Asynchronous:表示只是提供一個callback function,issue 網絡動作後,不需要專門的thread進行等待,網絡事件發生後,kernel會自動調用callback function (including signal-driven I/O, Asynchronous I/O,Overlapped with Completion Routine)。

一個小故事讓我們明白資金流通的意義

“又是炎熱小鎮慵懶的一天。太陽高掛,街道無人,每個人都債台高築,靠信用度日。這時,從外地來了一位有錢的旅客,他進了一家旅館,拿出一張1000 元鈔票放在櫃檯,說想先看看房間,挑一間合適的過夜,就在此人上樓的時候---- 店主抓了這張1000 元鈔,跑到隔壁屠戶那裡支付了他欠的肉錢...