preload
五月 08


這幾天, 因為無聊, 上網看了一些socket的function, 順手抄了一個簡單的socket程式.
但是這程式總是很不穩定.
怪怪! server端的程式, run 了第二次後, bind() 就會產生』Address already in use』的錯誤訊息.

上Google查了一下, 才知道bind() 之後, 若程式已經結束, 雖然已經正常把socket給close() 了,
但是kernel並不會release這個TCP 的connectioin, 它的狀態會keep 在』TIME_WAIT』狀態. (可以用netstat -t查詢)
要好幾分鐘後, kernel才會release 掉.
怪怪!! 搞不懂為何要做成這樣?? 算了! 找到解答就好.
在bind() 之前加上

setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, reuseaddr_len);

來告訴bind(), 若這個address 假設已經被佔用了, 那就重複使用吧.
完整的code 如下:
Server端:


#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>    /* signal name macros, and the signal() prototype */

#define DAEMON_LOCK "/tmp/hao_lock"
int listenFd, connectFd;
char    ttt[]="12345";

void catch_int(int sig_num);

pid_t CheckLock(void)
{
  pid_t me;
  FILE * fp; 

  fp = fopen(DAEMON_LOCK,"rt");
  if (fp==NULL) return 0;
  fscanf(fp,"%d",&me);
  fclose(fp); 

  return me;
} 

pid_t WriteLock(void)
{
  pid_t me;
  FILE *fp; 

  me = getpid(); 

  fp = fopen(DAEMON_LOCK,"w");
  fprintf(fp,"%d",me);
  fclose(fp); 

  return me;
} 

int CleanLock(void)
{
  return (unlink(DAEMON_LOCK)==0);
} 

int init_server(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
{
    int fd;
    int err = 0;
    int reuseaddr = 1;
    socklen_t reuseaddr_len;

    if((fd = socket(addr->sa_family, type, 0)) < 0)
        return (-1);

    reuseaddr_len = sizeof(reuseaddr);
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, reuseaddr_len);

    if(bind(fd, addr, alen) < 0) {
        err = errno;
        goto errout;
    }
    WriteLock();

    if((type == SOCK_STREAM) || (type == SOCK_SEQPACKET)) {
        if(listen(fd, qlen) < 0) {
            err = errno;
            goto errout;
        }
    }
    return (fd);
errout:
    close(fd);
    errno = err;
    return(-1);
}

int main(int argc, char *argv[])
{
    struct sockaddr_in serverAddr;
    pid_t   myself;

    time_t  ticks;
    char    buf[1024];

    listenFd = -1;
    connectFd = -1;

    /* find myself */
    myself = CheckLock();
    if (myself!=0) {
        printf("Existing a copy of chess daemon[pid=%d], leave now.\n",myself);
        exit(1);
    } 

    /* set the INT (Ctrl-C) signal handler to 'catch_int' */
    signal(SIGINT, catch_int);

    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    // any internet interface on ths server.
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port        = htons(13000);

    if((listenFd = init_server(SOCK_STREAM, (const struct sockaddr *)&serverAddr, sizeof(serverAddr), 5)) < 0) {
        perror("init_server");
        exit(1);
    }
    printf("Init Done..\n");
    for( ; ; ) {
        if((connectFd = accept(listenFd, NULL, NULL)) == -1) {
            perror("\n accept() error");
            exit(1);
        }
        fputs("Server: Connected!!\n",stdout);
        ticks = time(NULL);
        sprintf(buf,"%.24s\r\n", ctime(&ticks));
        write(connectFd, buf, strlen(buf));

        shutdown(connectFd, 2);
        close(connectFd);
    }
    return 0;
}

void catch_int(int sig_num)
{

    signal(SIGINT, catch_int);

    if(listenFd > 0)
        close(listenFd);
    CleanLock();

    printf("\n Done\n");
    exit(0);

}

Client端:


#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#define MAXSLEEP 128
int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen)
{

    int nsec;
    // Try to connect with exponential backoff.
    for(nsec = 1; nsec <= MAXSLEEP; nsec <<= 1) {
        if(connect(sockfd, addr, alen) == 0) {
            // Connection accepted.
            return(0);
        }
        // Delay before trying again.
        printf("Retry..\n");
        if(nsec <= MAXSLEEP/2)
            sleep(nsec);
    }
    return (-1);
}

int main(int argc, char *argv[])
{
    struct sockaddr_in serverAddr;
    int sockFd, n;
    char buf[1025];

    if(argc != 2) {
        fputs("usage: a.out \n", stdout);
        return -1;
    }

    // set a tcp/ip socket
    sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockFd <= -1) {
        perror("\n Create socket error!");
        exit(1);
    }

    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port   = htons(13000);
    // host IP # in dotted decimal format !
    inet_pton(AF_INET, argv[1], &serverAddr.sin_addr);

    connect_retry(sockFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));

    while(( n = read(sockFd, buf, sizeof(buf))) > 0) {
        buf[n] = 0;
        fputs(buf, stdout);
    }
    shutdown(sockFd, 2);
    close(sockFd);
    return 0;
}

以上的code, 大部分都是抄來的, 故不保證沒有問題.

若是使用AF_UNIX方式來連接, 那就不是上述方式來解決.
因為AF_UNIX方式會產生一個file, 只要離開程式之前, 呼叫unlink() 把它delete掉即可.
參考:
http://blog.chinaunix.net/u2/67414/showart_1889821.html