您的位置:首页 > 理论基础 > 计算机网络

TCP socket编程实现

2017-05-24 19:29 316 查看
下面是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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: