上节 我们课后作业代码:基于服务器客户端的三次数据传输:
服务端代码:
#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;