网络编程4

上节 我们课后作业代码:基于服务器客户端的三次数据传输:

服务端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	int clnt_sock;
	int str_len, i;

	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char msg1[]="Hello client!";
	char msg2[]="I'm server.";
	char msg3[]="Nice to meet you.";
	char* str_arr[]={msg1, msg2, msg3};
	char read_buf[100];
	
	if(argc!=2){
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_addr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
		error_handling("bind() error"); 
	
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_addr_size=sizeof(clnt_addr);  
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
	if(clnt_sock==-1)
		error_handling("accept() error");  
	
	for(i=0; i<3; i++)
	{
        //需要写入的字符串的长度
		str_len=strlen(str_arr[i])+1;
		write(clnt_sock, (char*)(&str_len), 4);
        //写入字符串
		write(clnt_sock, str_arr[i], str_len);
		
        //读取的字符串长度
		read(clnt_sock, (char*)(&str_len), 4);
        //读取的字符串
		read(clnt_sock, read_buf, str_len);
		puts(read_buf);
	}
	
	close(clnt_sock);
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;

	char msg1[]="Hello server!";
	char msg2[]="I'm client.";
	char msg3[]="Nice to meet you too!";
	char* str_arr[]={msg1, msg2, msg3};
	char read_buf[100];

	int str_len, i;
	
	if(argc!=3){
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);
	if(sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_addr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 
		error_handling("connect() error!");

	for(i=0; i<3; i++)
	{
		read(sock, (char*)(&str_len), 4);
		read(sock, read_buf, str_len);
		puts(read_buf);

		str_len=strlen(str_arr[i])+1;
		write(sock, (char*)(&str_len), 4);
		write(sock, str_arr[i], str_len);
	}
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

前三文讲述了TCP套接字 下面接着讲述UDP套接字

即UDP的结构和传输方式都比TCP简单,没有TCP那些握手协议等等。UDP性能比TCP高。TCP和UDP最重要的差别是 UDP没有TCP在不可靠的IP层进行流控制机制。

UDP的内部工作原理如下图:

UDP有一定的可靠性,不是完全没有可靠性,TCP比UDP慢主要有以下两点:

1.收发数据前后进行的连接设置即清除过程

2.收发数据过程中为保证可靠性而添加的流控制。

下面将讲述UDP的客户端和服务端的使用:

尤其要注意没有listen和accept的函数使用

并且UDP服务器只需要一个套接字,不像TCP服务器中套接字是一对一关系 。

下面讲述UDP的IO函数:

在创建TCP套接字时已经赋予了IP地址和端口,传输数据时,无需指定地址,因为TCP套接字知道对方目的地,但是UDP不知道,因此每次传输数据时会要传送目标地址信息。

与TCP的输出函数相比UDP要传输目的地的IP地址。

下面将介绍接受数据的函数:

下面给出基于UDP套接字服务器代码;

#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
const int MAX=30;
int main(int argc,char* argv[]){
    int serv_sock;
    char message[MAX];
    int str_len;
    socklen_t cil_len_size;
    struct sockaddr_in servaddr,ciladdr;
    if(argc!=2){
        std::cout<<"error"<<std::endl;
        exit(1);
    }
    serv_sock=socket(PF_INET,SOCK_DGRAM,0);//udp类数据传输;
    if(serv_sock==-1){
        std::cout<<"socket()error"<<std::endl;
    }
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(atoi(argv[1]));
    if(bind(serv_sock,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1){
        std::cout<<"bind()error"<<std::endl;
    }
    //已经创建好UDP服务器的套接字
    while(1){
        cil_len_size=sizeof(ciladdr);//计算储存客户端的套接字的长度
        str_len=recvfrom(serv_sock,message,MAX,0,(struct sockaddr*)&ciladdr,&cil_len_size);//和accept函数 类似 服务器套接字还是类似于门卫 ciladdr储存客户端的地址信息 cil_len_size储存的是结构体长度。
        sendto(serv_sock,message,str_len,0,(struct sockaddr*)&ciladdr,cil_len_size);
    }
    close(serv_sock);
    return 0;
}

下面是客户端的代码:

#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
using namespace std;
const int MAX_SIZE=30;

int main(int argc,char* argv[]){
    int sock;
    char message[MAX_SIZE];
    int str_len;
    struct sockaddr_in serv_adr,from_adr;
    socklen_t adr_size;
    if(argc!=3){
        cout<<"error"<<endl;
        exit(1);
    }
    sock=socket(PF_INET,SOCK_DGRAM,0);
    if(sock==-1){
        cout<<"socket()<<error"<<endl;
    }
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
//无connect函数
while(1){
    fputs("insert message(q to quit):",stdout);
    fgets(message,sizeof(message),stdin);

    if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"));
    break;//因为含有空格 所以包含\n
    sendto(sock,message,strlen(message),0,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
adr_size=sizeof(from_adr);
str_len=recvfrom(sock,message,MAX_SIZE,0,(struct sockaddr*)&from_adr,&adr_size);
message[str_len]=0;
cout<<message<<endl;
}
close(sock);
return 0;
}

上述中我们发现UDP在调用时,为对套接字分配目标主机的地址和端口号,在TCP服务端中是通过调用connect函数自动赋予的,UDP是调用SENDTO函数时赋予的。

对于TCP服务器,双方调用IO函数的次数不是很重要,但是对于UDP服务器,双方调用的IO函数操作次数必须要相同 比如客户端发送三次信息,服务端就必须要接受三次信息。

即如果数据过大,数据不会被分为多次发送。这与TCP不同。

但是如果对同一台机器进行连续的传输数据,使用UDP套接字 这样就会多次执行步骤1,3.所以对于相同双方的多次操作,可以提前用connect函数赋予UDP套接字目标地址和端口号。

注意UDP套接字可以调用四种IO函数 这十分重要。运用了connect函数调用后 可以用read和write函数代替sendto和recfrom两个函数。

下面将介绍套接字的半断开:

对于UDP和TCP,在客户端与服务端在相互传递信息的时候,相当于两个独自形成了IO流。

如果对于如下情况,一方只传递数据 一方只接受数据,在这样的情况下就需要套接字的半断开。

如上图:如果在传输文件数据后断开套接字,之后客户端传输的thank you无法接受到 这样是不行的,所以需要半传输。下面给出半传输的客户端和服务端的代码。

服务端:

#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include<cstdio>
const int MAX_SIZE=1024;
using namespace std;
int main(int argc,char* argv[]){
int serv_id,cil_id;
FILE* fp;
char buf[MAX_SIZE];
struct sockaddr_in serv_adr,cil_adr;
socklen_t cil_size;
int read_cnt;
if(argc!=2){
    cout<<"error"<<endl;
    exit(1);
}
fp=fopen("serv.cpp","rb");//打开服务端文件 向客户端发送服务端源文件
serv_id=socket(PF_INET,SOCK_STREAM,0);
if(serv_id==-1){
    cout<<"socket()error"<<endl;
}
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
bind(serv_id,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
listen(serv_id,5);
cil_size=sizeof(cil_adr);
cil_id=accept(serv_id,(struct sockaddr*)&cil_adr,&cil_size);

//已经连接了
while(1){
read_cnt=fread((void*)buf,1,MAX_SIZE,fp);
if(read_cnt<MAX_SIZE){
    write(cil_id,buf,read_cnt);
    break;
}
write(cil_id,buf,MAX_SIZE);
}//向客户端不断发送文件

shutdown(cil_id,SHUT_WR);//关闭服务端的输入流
read(cil_id,buf,MAX_SIZE);//服务端不能再向客户端传输文件 但是可以接受文件。
cout<<buf<<endl;
fclose(fp);
close(serv_id);
close(cil_id);
return 0;
}

下面是客户端的代码:

#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
const int MAX=30;
int main(int argc,char* argv[]){
    int sd;
    FILE* fp;
    int read_cnt;
    char buf[MAX];
    struct sockaddr_in serv_adr;
    if(argc!=3){
        std::cout<<"error"<<std::endl;
        exit(1);
    }
    fp=fopen("yibanser.cpp","wb");//创造新文件
    sd=socket(PF_INET,SOCK_STREAM,0);
    memset(&serv_adr,0,sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_adr.sin_port=htons(atoi(argv[2]));
    connect(sd,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
//建立完成
while((read_cnt=read(sd,buf,MAX))!=0)
 fwrite((void*) buf,1,read_cnt,fp);//先读取新文件存在BUF中 然后再写入新的文件中

std::cout<<"recver file date"<<std::endl;
write(sd,"thank you",10);//九个字母加一个/0  向服务端传输 服务端只是关闭了输出流 所以还是可以接受的。 
fclose(fp);
close(sd);
return 0;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值