基本TCP套接字编程

在学习具体函数前,必须先有这样一个认识,socket和各种相关函数的实质是什么?socket源于Unix,而Unix有着“一切皆文件”的哲学思想,socket是“open-write/read-close”模式的实现,那么socket就会提供接口函数来实现对其进行相应操作。

 

1.socket():

在成功时返回一个小的非负整数值,它与文件描述符类似,称为套接字描述符

 

2.connect():

在此之前,不必非得调用bind函数,因为需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。

如果是TCP套接字,调用connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或失败时才返回。

connect函数导致当前套接字从CLOSED状态(该套接字自从由socket函数创建以来一直所处状态)转移到SYN_SENT状态;若connect成功再转移到ESTABLISHED状态若失败则该套接字不再可用,必须关闭,不能对这样的套接字再次调用connect函数。

 

3.bind():

把一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDP端口号的组合

TCP服务器不同于TCP客户,需要捆绑它们的众所周知端口被大家认识。

 

4.listen():

仅由TCP服务器调用。当socket函数创建套接字时,它被假设为一个主动套接字,即一个将调用connect发起连接的客户套接字。而listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的连接请求。

调用listen导致套接字从CLOSED状态转换到LISTEN状态

另外,必须认识到内核为任何一个给定的监听套接字维护两个队列

  • 未完成连接队列:每个SYN分节对应其中一项,已由某个客户发出并到达服务器,而服务器正在等待完成相应TCP三次握手过程,这些套接字处于SYN-RCVD状态
  • 已完成连接队列:每个已完成TCP三次握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态

 

5.accept():

由TCP服务器调用,用于从已完成连接队列返回下一个已完成连接,若队列为空则进程投入睡眠,直到TCP在该队列中投入一项才唤醒它。

那么accept正确返回后,接下来发生了什么?

如果accept成功,那么其返回值是一个内核自动生成的全新描述符,代表与所返回客户的TCP连接。

讨论accept函数时,作为参数的是监听套接字描述符listenfd(由socket创建,随后用作bind和listen参数的描述符),作为返回值的是已连接套接字描述符connfd:一个服务器仅创建一个监听套接字,它在服务器生命期内一直存在,而内核为服务器进程接受的每个客户连接创建一个已连接套接字,当服务器完成对某给定客户的服务时,相应已连接套接字就被关闭(结合下面论述理解:已连接套接字在每次循环中关闭,但监听套接字在整个有效期内保持开放)

 

6.并发编程机制

Unix中编写并发服务器程序最简单的办法就是fork一个子进程来服务每个客户:

图注:

3中父进程关闭connfd,子进程关闭listenfd;

4中达到两个套接字所期望的最终状态,即子进程处理与客户连接,父进程可在监听套接字上再次调用accept来处理下一个客户连接,这从1中服务器和4中服务器父进程对照可看出。

结合上面处理流程就不难理解“对一个TCP套接字调用close会导致发送一个FIN,随后是正常的TCP连接终止序列,但为什么父进程对connfd调用close没有终止与客户的连接?”这个问题,因为在每个文件或套接字都有一个引用计数(在文件表项中维护),它是当前打开着的引用该文件或套接字的描述符个数。如:socket返回后与listenfd关联的文件表项的引用计数值为1,accept返回后与connfd关联的文件表项的引用计数值也为1;在fork返回后,两个描述符在父子进程间共享,因此引用计数值均为2。这么一来,当父进程关闭connfd时,它只是把引用计数值从2减为1,该套接字真正的清理和资源释放要等到引用计数值为0时才发生

/*-------------------------------------------------
 * Name:            head.h
 * Date:            2015/10/08
 ------------------------------------------------*/
#ifndef _HEAD_H_
#define _HEAD_H_

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>

#include <time.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#include <sys/time.h>
#include <arpa/inet.h>

void perror_exit(char *s)
{
    perror(s);
    exit(EXIT_FAILURE);
}

#endif
/*-------------------------------------------------
 * Name:            server.c
 * Date:            2015/10/08
 ------------------------------------------------*/
#include "head.h"

int main(int argc, char *argv[])
{
    int listenfd;
    listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listenfd < 0)
        perror_exit("socket error");
    else printf("create listenfd successfully...\n");

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(5188);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //inet_aton("127.0.0.1", $serveraddr.sin_addr);

    int on=1;
    if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))<0)
        perror_exit("setsockopt error");

    if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)
        perror_exit("bind error");
    else printf("bind address successfully...\n");

    if(listen(listenfd, SOMAXCONN) < 0)
        perror_exit("listen error");
    else printf("keep listening...\n");

    struct sockaddr_in peeraddr;
    socklen_t peerlen = sizeof(peeraddr);
    int connfd;
    int count = 0;
    pid_t pid;
    while(1)
    {
        if((connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
            perror_exit("accept error");
        else printf("client %d connect to server: ip = %s, port = %d\n", ++count, inet_ntoa(peeraddr.sin_addr), peeraddr.sin_port);

        pid = fork();
        if(pid == -1)
            perror_exit("fork error");
        else if(pid > 0)
            close(connfd);
        else
        {
            close(listenfd);
            while(1)
            {
                char buf[1024];
                memset(buf, 0, sizeof(buf));
                int ret = read(connfd, buf, sizeof(buf));
                if(ret == -1)
                    perror_exit("read error");
                else if(ret == 0)
                {
                    printf("client %d was closed...\n", count);
                    break;
                }
                else
                {
                    fputs(buf, stdout);

                    int len = strlen(buf);
                    buf[len-1] = '*';
                    buf[len] = '\n';

                    write(connfd, buf, ret);
                }
            }
            exit(EXIT_SUCCESS);
        }
    }
    return 0;
}
/*-------------------------------------------------
 * Name:            client.c
 * Date:            2015/10/08
 ------------------------------------------------*/
#include "head.h"

int main(int argc, char *argv[])
{
    int socketfd;
    socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(socketfd == -1)
        perror_exit("socket error");
    else printf("create socketfd successfully...\n");

    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_port = htons(5188);
    clientaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int conn;
    conn = connect(socketfd, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
    if(conn == -1)
        perror_exit("connect error");
    else printf("connect to server successfully...\n");

    char recvbuf[1024];
    char sendbuf[1024];
    memset(recvbuf, 0, sizeof(recvbuf));
    memset(sendbuf, 0, sizeof(sendbuf));

    int ret;
    pid_t pid;
    pid = fork();

    if(pid == -1)
        perror_exit("fork error");
    else if(pid > 0)
    {
        while(1)
        {
            ret = read(socketfd, recvbuf, sizeof(recvbuf));
            if(ret == -1)
                perror_exit("read error");
            else if(ret == 0)
            {
                printf("server was closed...\n");
                break;
            }
            else
                fputs(recvbuf, stdout);
        }
        close(socketfd);
    }
    else
    {
        while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
        {
            write(socketfd, sendbuf, sizeof(sendbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }
        exit(EXIT_SUCCESS);
    }
    return 0;
}

posted on 2015-07-04 23:07  huashunli  阅读(240)  评论(0编辑  收藏  举报

导航