TCP socket编程实现

下面是TCP socket的经典流程图
服务器端依次调用socket()、bind()、listen()之后,开始监听指定的socket地址。客户端调用socket(),然后再调用connect()向服务器发送一个连接请求。服务器监听到这个请求之后,就会调用accept()完成接收请求,并且返回一个新的套接字描述符,可用于向客户端读取或发送数据,用的分别是recv()和send()函数。同样,客户端connect()成功之后,也可以向服务器读取或发送数据。
这里写图片描述

接下来,客户端和服务器端分别写一个程序,然后在不同的终端中运行试验。

/*client.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>

#define PORT 8019
#define BUFFER_SIZE 4096
#define FILE_NAME_MAX 512

int main(int argc,char* argv[])
{
    int sockfd;
    //char buff[BUFFER_SIZE];
    struct hostent *host;
    struct sockaddr_in serv_addr;

    if(argc != 2)
    {
        fprintf(stderr,"Usage: ./client Hostname(or ip address) \ne.g. ./client 127.0.0.1 \n");
        exit(1);
    }

    //地址解析函数
    if ((host = gethostbyname(argv[1])) == NULL)
    {
        perror("gethostbyname");
        exit(1);
    }
    //创建socket
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    bzero(&serv_addr,sizeof(serv_addr)); 
    //设置sockaddr_in 结构体中相关参数
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT); //将16位的主机字符顺序转换成网络字符顺序
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr); //获取IP地址
    bzero(&(serv_addr.sin_zero), 8);  //填充0以保持struct sockaddr同样大小

    //调用connect函数主动发起对服务器端的连接
    if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr))== -1)
    {
        perror("connect");
        exit(1);
    }

    char buff[BUFFER_SIZE]= "<letter>getPwd</letter>"; //读写缓冲区
    int count;
    count=send(sockfd,buff,100,0);
    if(count<0)
    {
        perror("Send file informantion");
        exit(1);
    }
    printf("client send OK count = %d\n",count);
    //接收来自server的数据
    char pbuf[BUFFER_SIZE] = {0};
    int length=recv(sockfd,pbuf,100,0);
    printf("data from pon is %s\n",pbuf);
    close(sockfd);
    return 0;
}

从上面的程序可以看到,客户端创建socket,然后向服务器发出connect,连接成功后,
会发送字符串”getPwd“到服务器,然后等待接收来自服务器的反馈数据。

/*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
/*
如果把IP地址比作一间房子 ,端口就是出入这间房子的门。端口号就是打开门的钥匙。
真正的房子只有几个门,但是一个IP地址的端口 可以有65536个之多!
端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535。
地址中的端口号必须不小于1024,除非该进程具有相应的特权(即超级用户)。
*/
#define PORT 8019
#define RCV_BUF_SIZE 0x1000
#define MAX_QUE_CONN_NM 1

char * getPwdFunc()
{
    //获取密码
    printf("Began to get the password\n ");
    printf("Send the parsed msg to client\n ");
    char * result = "result=ok|id=test|pwd=test";
    return result;
}

char * setPwdFunc()
{
    printf("Began to get the password\n ");
    printf("Send the parsed msg to client\n ");
    char * result = "result=ok";
    return result;
}

typedef char *(*func)();
struct processFun {
    char *name;
    func process;
};

struct processFun cmdFunc[] = {
    {"setPwd",         setPwdFunc},
    {"getPwd",         getPwdFunc}
};

char * client_acquire_server_info(char *msg)
{
    char *pMsgTmp;
    int i;
    for(i=0; i< sizeof(cmdFunc)/sizeof(struct processFun); i++)
    {
        if(0 == strcmp(msg,cmdFunc[i].name))
        {
            printf("found it \n");
            printf("%s: parameter %s \n",__FUNCTION__,msg);
            pMsgTmp = cmdFunc[i].process();
            break;
        }
    }
    return pMsgTmp;
}

//获取消息主体 
char *getBodyOfMsg(char *buf)
{
    char * pStr1 = NULL;
    char * pStr2 = NULL;
    pStr1 = strstr(buf,"<letter>");
    pStr2 = strstr(buf,"</letter>");
    if (pStr2 == NULL || pStr1 == NULL)
    {
        return NULL;
    }
    *pStr2 = '\0';
    return (pStr1 + strlen("<letter>"));
}

//解析接收到的字符串,然后调用相应的zte接口返回相关数据发送给客户端
char * parseDataFromClient(char *pmsg)
{
    char *pTmp = getBodyOfMsg(pmsg);
    if (pTmp == NULL)
    {
        printf("parse data failed!\n");
        return NULL;
    }
    printf("the parsed data is %s \n",pTmp);
    char * acquireDataFromServer = client_acquire_server_info(pTmp);
    return acquireDataFromServer;
}

int main(int argc,char* argv[])
{
    int sockfd;
    struct sockaddr_in server_sockaddr, client_sockaddr;
    //建立socket连接
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
    {
        perror("socket");
        exit(1);
    }
    printf("Socket id = %d\n",sockfd);

    //设置sockaddr_in 结构体中相关参数
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);

    //setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

    //绑定函数bind
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)
    {
        perror("bind");
        exit(1);
    }
    printf("Bind success!\n");

    //调用listen函数
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");

    char *pbuf = NULL;
    int client_fd,length;
    int addrlen = sizeof(server_sockaddr);
    char *ptmp = NULL;
    pbuf = (char *)malloc(RCV_BUF_SIZE);
    if(NULL == pbuf)
    {
        printf("igdserver:main:malloc failed in callback\n");
    }
    while(1)
    {
        if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, (socklen_t *)&addrlen)) == -1)
        {
            perror("accept");
            exit(1);
        }
        memset(pbuf, RCV_BUF_SIZE, 0);
        length=recv(client_fd,pbuf,100,0);
        printf("string from client is %s ,length = %d\n",pbuf,length);
        //服务器开始解析从客户端传过来的数据,并做出相应的处理
        ptmp = parseDataFromClient(pbuf);
        //把得到的结果返回给客户端
        int count=send(client_fd,ptmp,100,0);
        if(count<0)
        {
            perror("Send file informantion");
            exit(1);
        }
        printf("client send OK, count = %d,result = %s\n",count,ptmp);  
    }
    free(pbuf);
    close(sockfd);
    close(client_fd);
    return 0;
}

程序干了几件事:
1. 服务器创建socket,调用bind绑定地址,listen监听客户端
2. 当监听到客户端的连接,accept返回新的套接字描述符client_fd,通过client_fd,用recv()函数可以接收到客户端发送过来的数据”getPwd”
3. 解析数据,调用getBodyOfMsg得到来自客户端的命令为”getPwd”。调用client_acquire_server_info(char *msg)处理命令,这个函数会去查找匹配到cmdFunc[]数组中的{“getPwd”, getPwdFunc},找到客户端发送命令相对应的getPwdFunc()函数并执行这个函数。
4. 把获取到的密码通过send()函数发送到客户端。

这样写的好处是,以后如果客户端指定更多的命令,服务器端只需要在cmdFunc[]添加相对应的命令和函数,使得程序扩展方便。

接下来实验一下程序。打开终端编译程序,然后执行./server

//终端A:
ubuntu:~/test/aa$ gcc client.c -o client
ubuntu:~/test/aa$ gcc server.c -o server
ubuntu:~/test/aa$ ls
client  client.c  server  server.c
ubuntu:~/test/aa$ ./server
Socket id = 3
Bind success!
Listening....

打开另外一个终端,执行./client 127.0.0.1
得到来自服务器端获取的账号test和密码test

//终端B:
ubuntu:~/test/aa$  ./client 127.0.0.1 
client send OK count = 100
data from pon is result=ok|id=test|pwd=test

这时查看执行server程序的终端,查看下面打印,可以发现接收客户端的命令是getPwd ,并且程序做出相应处理后把数据发送给了客户端

 //终端A:
string from client is <letter>getPwd</letter> ,length = 100
the parsed data is getPwd 
found it 
client_acquire_server_info: parameter getPwd 
Began to get the password
Send the parsed msg to client
client send OK, count = 100,result = result=ok|id=test|pwd=test
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值