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

基于TCP的客户端、服务器端socket编程

2017-12-13 22:35 330 查看

基于TCP的客户端、服务器端socket编程

一、实验目的

理解tcp传输客户端服务器端通信流程

二、实验平台

MAC OS

gxx-include-dir=/usr/include/c++/4.2.1

三、实验内容

编写TCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦连接成功,则显示客户的IP地址、端口号,并向客户端发送字符串。

四、实验原理

使用TCP套接字编程可以实现基于TCP/IP协议的面向连接的通信,它分为服务器端和客户端两部分,其主要实现过程如下



1、socket函数

在网络编程中所需要进行的第一件事情就是创建一个socket,无论是客户端还是服务器端,都需要创建一个socket,该函数返回socket文件描述符,类似于文件描述符。socket是一个结构体,被创建在内核中。

sockfd=socket(AF_INET,SOCK_STREAM,0);   //AF_INT:ipv4, SOCK_STREAM:tcp协议


2、connect函数

客户端创建了socket后,需要和服务器端建立连接,此时使用connect函数和服务器端进行连接。

connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))


三次握手:

第一次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

3、bind函数

把一个本地协议地址和套接口绑定,比如把本机的2222端口绑定到套接口。注意:为什么在上图中客户端不需要调用bind函数?这是因为如果没有调用bind函数绑定一个端口的话,当调用connect函数时,内核会为该套接口临时选定一个端口,因此可以不用绑定。而服务器之所以需要绑定的原因就是,所以客户端都需要知道服务器使用的哪个端口,所以需要提前绑定。

bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))


4、listen函数

当socket创建后,它通常被默认为是主动套接口,也就是说是默认为要马上调用connect函数的,而作为服务器是需要被动接受的,所以需要调用linsten函数将主动套接口转换成被动套接口。调用linsten函数后,内核将从该套接口接收连接请求。

/**
* 3:调用listen函数监听(指定port监听)
* 通知操作系统区接受来自客户端链接请求
* 第二个参数:指定队列长度
*/

if(listen(sockfd,10) < 0)
{
perror("listen error");
}


5、accept函数

此函数返回已经握手完成的连接的套接口。注意:此处的套接口不同于服务器开始创建的监听套接口,此套接口是已经完成连接的套接口,监听套接口只是用来监听。

accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);


6、write函数

c8c8
调用IO函数(read/write)和连接的客户端进行双向通信。

long t = time(0);
char *s = ctime(&t);
size_t size = strlen(s) * sizeof(char);
//将服务器的系统时间写到客户端
if(write(fd,s,size) != size)
{
perror("write error");
}


7、close函数

数据传输完成后,需要关闭套接口

//关闭socket
close(fd);


linux网络编程之用一张图片说明套接口常用函数



五、实验流程

服务器端代码

#include "iostream"
#include "netdb.h"
#include "stdio.h"
#include "stdlib.h"
#include "sys/socket.h"
#include "unistd.h"
#include "arpa/inet.h"
#include "string.h"
#include "memory.h"
#include "signal.h"
#include "time.h"

using namespace std;

int sockfd;

void sig_handler(int signo)
{
if(signo == SIGINT)
{
cout<<"Server close"<<endl;
close(sockfd);
exit(1);
}
}

//输出链接上来的客户端相关信息
void out_addr(struct sockaddr_in *clientaddr)
{
//将断口从网络字节序转成主机字节序
int port = ntohs(clientaddr->sin_port);
char ip[16];
memset(ip,0,sizeof(ip));
inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
cout<<"client:"<<ip<<"("<< port <<")connected\n"<<endl;
}

void do_service(int fd)
{
//获取系统时间
long t = time(0);
char *s = ctime(&t);
size_t size = strlen(s) * sizeof(char);
//将服务器的系统时间写到客户端
if(write(fd,s,size) != size)
{
perror("write error");
}
}

int main(int argc,char *argv[])
{
if(argc<2)
{
cout<<"usage:"<<argv[0]<<"#port"<<endl;
exit(1);
}

if(signal(SIGINT,sig_handler) == SIG_ERR)
{
perror("signal sigint error");
exit(1);
}

/**
* 1.创建socket
* AF_INET:ipv4
* SOCK_STRAM:tcp协议
*/
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket error");
exit(1);
}

//2.调用bind函数绑定socket和地址

struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
//往地址中填入ip,port,internet类型
serveraddr.sin_family = AF_INET;    //ipv4
serveraddr.sin_port = htons(atoi(argv[1])); //htons主机字节序转成网络字节序

serveraddr.sin_addr.s_addr = INADDR_ANY;

if(bind(sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind error");
exit(1);
}

/**
* 3:调用listen函数监听(指定port监听)
* 通知操作系统区接受来自客户端链接请求
* 第二个参数:指定队列长度
*/

if(listen(sockfd,10) < 0)
{
perror("listen error");
}

/**
* 4:调用accept函数从队列中
* 获得一个客户端的请求链接
*/

struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);

while(1)
{
int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
if(fd < 0)
{
perror("accept error");
continue;
}

/**
* 5:调用IO函数(read/write)和
* 连接的客户端进行双向通信
*/
out_addr(&clientaddr);
do_service(fd);

//关闭socket
close(fd);
}

return 0;
}


客户端代码

#include "netdb.h"
#include "sys/socket.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "memory.h"
#include "unistd.h"
#include <arpa/inet.h>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
if(argc < 3)
{
cout<< "usage:"<<argv[0]<<" ip port"<<endl;
exit(1);
}

//步骤1:创建socket
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket error");
exit(1);
}

struct sockaddr_in serveraddr;
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));

//主机字节序转换成网络字节序
inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);

//步骤2:客户端调用connect函数连接到服务器
if(connect(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0)
{
perror("connect error");
exit(1);
}

//步骤3:调用IO函数(read/write)和服务器端双向通信
char buffer[1024];
memset(buffer,0,sizeof(buffer));
size_t size;

if((size=read(sockfd,buffer,sizeof(buffer)))< 0)
{
perror("read error");
}

if(write(STDOUT_FILENO,buffer,size)!=size)
{
perror("write error");
}

return 0;
}


实验结果



思考

在进行网络通信时是否需要进行字节序转换:相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。

参考文献

基于TCP的客户端、服务器端socket编程

Socket_编程_参考

linux网络编程之用socket实现简单客户端和服务端的通信(基于TCP)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: