Socket的基本编程模型

基本的编程模型分为流式和数据报两种,这两种模型都可以用于互联网上的主机进行通信以及同一主机内的不同进程之间的通信,当这两种方式应用于互联网通信时,底层分别使用了两种协议:TCP和UDP。


描述

服务器创建一个主Socket,通过bind方法绑定IP和端口,通过accept函数为每一个发起请求的客户端创建一个新的Socket;

操作系统为每一个Socket创建一块缓存,程序通过read/write读写缓存数据,Socket之间互不影响(Apache的服务器实现是把每一个Socket分配到一个线程上进行处理)

当客户端正常close时会给服务器发送一个FIN标志,服务器端在执行读操作时会发现read返回0,write操作返回-1(errno表示管道中断)

但如果客户端异常,服务端仅执行read操作则无法获知,曾经执行过read操作的话一定时间内可以获知,为确保客户端异常,服务端可以及时释放资源要么进行应用程序的心跳要么设置保活记时器


描述

数据报模型的服务器端从始至终只有一个Socket,既要负责绑定端口,也要负责每个客户端的数据读写

唯一的Socket它的缓存不是流而是一个消息队列,每次执行recvform的时候从消息队列里拉出第一条(一般是根据服务器收到消息的时间进行入队,先到先进队列)如果消息内容太长,超过recvform参数指定的buffer大小,则消息内容被截断

客户端正常的close和崩溃从recvfrom和sendto没有办法进行体现,所以必须要在应用程序进行显式的处理

异常设计

需求:

用C#写一个控制台程序监听服务器的8100端口,一旦发现该接口的请求则重启IIS,并将是否成功的消息返回给请求方

实现:

Program.cs
程序入口,组织逻辑
Shell.cs
执行命令行程序
Listener.cs 
监听8100端口
    class Program
    {
        static int port = 8100;
        static void Main(string[] args)
        {
            // 最外层异常,只是为了防止编程错误和未意料异常
            try
            {
                Func<String, bool> execute = (String s) =>
                {
                    // 最内层的异常处理,只处理启动iis失败的异常
                    try
                    {
                        Console.WriteLine("");
                        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
                        String result = Shell.Execute("iisreset /restart");
                        Console.Write(result);
                        return true;
                    }
                    catch (Exception ex){
                        Console.Write("Command Error" + ex.Message);
                        return false;
                    }
                };

                Console.WriteLine("Bind Port " + port);
                Listener listener = new Listener(port);
                listener.Listen(execute);
            }
            catch (Exception ex) {
                Console.WriteLine("Abort: " + ex.Message);
            }
        }
    }
class Listener
{
    Socket socket;
    public Listener(int port) {
        IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, port);
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.Bind(endPoint);
        socket.Listen(0);
    }

    ~Listener() {
        socket.Close();
    }

    public void Listen(Func<String, bool> execute) {
        byte[] buffer = new byte[1024];
        while (true) {
            Socket requestSocket = null;
            // 只有通信异常才是“异常”,iis重启异常只能算作重启失败,记录就可以了
            try {
                requestSocket = socket.Accept();
                requestSocket.Receive(buffer);
                String requestString = Encoding.ASCII.GetString(buffer);
                String resultCode = execute(requestString) ? "OK" : "ERROR";
                requestSocket.Send(System.Text.Encoding.Default.GetBytes(resultCode));
            }
            catch (Exception ex) {
                Console.WriteLine("Socket Exception: " + ex.Message);
            }
            finally {
                if (requestSocket != null) {
                    requestSocket.Close();
                }
            }
        }
    }
}

Shell.cs

省略,主要功能是为了调用在命令行下能执行的程序,和我主题无关

管道和进程通信

说明

lftp是一个linux下的命令行工具,linux操作系统一般都自带这个工具。这个工具可以通过SSH协议和其他多种协议进行文件传输。我写了一个程序启动lftp进程,通过管道给这个进程下达传输指令,获取传输进度,这个程序是一个原理验证性的程序,原理验证之后就可以封装成一个真正能用的工具,可以通过socket和外部通信,命令下达到lftp进程,也可以写成一个动态链接库,JAVA通过JNI编程接口调用这个C程序。

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

int orderdes[2];
int outputdes[2];

void start_client(){

    printf("start client...\n");

    if(dup2(orderdes[0], STDIN_FILENO) == -1)
    {
        printf("dup2 stdin failed\n");
        exit(-1);
    }

    if(dup2(outputdes[1], STDOUT_FILENO) == -1)
    {
        printf("dup2 stdout failed\n");
        exit(-1);
    }

    char *args[4] = {"-f", "-c", "/usr/bin/lftp", NULL};
    int n = execv("/usr/bin/script", args);
    printf("return %d", n);
}

void question()
{
    for(;;)
    {
        char ss[1024] = {0};
        gets(ss);
        strcat(ss, "\n");
        write(orderdes[1], ss, strlen(ss));
    }
}

void output()
{
    for(;;)
    {
        char buffer[1024] = {0};
        read(outputdes[0], buffer, 1024);
        printf(buffer);
    }
}

int main()
{
    if (pipe(orderdes) == -1 || pipe(outputdes) == -1)
    {
        printf("open pipe failed\n");
        exit(-1);
    }

    switch(fork())
    {
        case -1:
            exit(-1);

        case 0:
            start_client();
            break;

        default:

            switch (fork())
            {
                case -1:
                    exit(-1);

                case 0:
                    output();
                    break;

                default:
                    question();
            }
    }

    return 0;
}

C语言日志函数

要求:实现一个打印日志信息的函数,支持可变参数,调用时生成日志内容。

void log(char *format, ...){
    va_list  args;
    va_start(args, format);

    static char buffer[BUF_SIZE - 100] = {0};
    memset(buffer, 0, BUF_SIZE);
    vsprintf(buffer, format, args);
    va_end(args);

    time_t now = time(NULL);
    static char message[BUF_SIZE] = {0};
    memset(message, 0, BUF_SIZE);
    strftime (message, sizeof(message), "[%Y-%m-%d %H:%M:%S] ", localtime(&now));

    strcat(message, buffer);
    strcat(message, "\n");
    printf(message);
    if(FILE_HANDLER != NULL){
        fwrite(buffer, strlen(buffer), sizeof(char), FILE_HANDLER);
    }
}
log("TCP Buffer empty : %d, %s", errno, strerror(errno));