网络程序设计——(connectTCP和connectUDP的实现)2.利用TCP/UDP完成文件传输的设计和实现(下)
每个客户与服务器建立联系必须:
- 选择协议(UDP或TCP) 查找服务器的机器名
- 查找所期望的服务并将其映射到协议端口号
- 分配套接字并与之连接
- 将这部分工作进行封装,置于某个过程当中,只需一次编码。
我们想要实现以下抽象:
- socket = connectTCP(machine, service);
- socket = connectUDP(machine, service);
优点:高级操作,共享代码,减少出错
实现代码如下:
1.connect.c文件
int connectsock(const char *host, const char *service,
const char *transport);
int connectTCP(const char *host, const char *service)
{
return connectsock(host, service, "tcp");
}
int connectUDP(const char *host, const char *service)
{
return connectsock(host, service, "udp");
}
2.connectsock.c文件:
//connectsock.c
//created by cck
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>//替换extern int errno;
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif // INADDR_NONE
int errexit(const char *format, ...);
int connectsock(const char * host, const char *service, const char *transport)
{
struct hostent *phe;
struct servent *pse;
struct protoent *ppe;
struct sockaddr_in sin;
int s, type;
memset(&sin,0,sizeof(sin));
sin.sin_family = AF_INET;
/*map port*/
if( pse = getservbyname(service, transport) )
sin.sin_port = pse->s_port;
else if ( (sin.sin_port=htons((unsigned short)atoi(service))) == 0)
errexit("can't get \"%s\" service entry\n", service);
/*map ip*/
if ( phe = gethostbyname(host) )
memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )
errexit("can't get \"%s\" host entry\n", host);
/*map protocol*/
if ( (ppe = getprotobyname(transport)) == 0)
errexit("can't get \"%s\" protocol entry\n", transport);
/*use protocol to chose a socket type*/
if(strcmp(transport,"udp") == 0)
type = SOCK_DGRAM;
else
type = SOCK_STREAM;
/*allocate a socket*/
s = socket(PF_INET, type, ppe->p_proto);
if (s < 0)
errexit("can't create socket: %s\n", strerror(errno));
if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
errexit("can't connect to %s.%s: %s\n", host, service,strerror(errno));
return s;
}
3.errexit.c
//errexit.c
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
int errexit(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(1);
}
以上是connectTCP、connectUDP的实现函数,以下是使用其实现TCP文件传输的代码:
4.tcp_client2.c
//tcp_client2.c
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 1024
void process_conn_client(int s)
{
ssize_t size = 0;
char buffer[BUFFER_SIZE];
FILE *stream;
int length = 0;
char filepath[100] = {'\0'};
size = read(s, buffer, BUFFER_SIZE);
printf("%s",buffer);
scanf("%s",filepath);
write(s,filepath,100);
if( (stream = fopen(filepath,"r")) == NULL) {
printf("client:open file error!\n");
return;
}
printf("sending!\n");
while(1){
size = fread(buffer,sizeof(char),BUFFER_SIZE,stream);
if(size <= 0){
break;
}
write(s,buffer,size);
}
printf("send finished!\n");
fclose(stream);
}
int main(){
int socket = connectTCP("localhost","8888");
process_conn_client(socket);
close(socket);
}
抽象封装之后,main函数只需要3行代码即可搞定
5.tcp_server.c
使用上篇的代码:
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#define PORT 8888
#define BUFFER_SIZE 1024
void get_filename(char *filepath,char *filename)
{
/*解析文件名*/
int i=0,k=0;
for(i=strlen(filepath);i>=0;i--)
{
if(filepath[i]!='/')
{
k++;
}
else
break;
}
strcpy(filename,filepath+(strlen(filepath)-k)+1);
}
void process_conn_server(int sd)
{
ssize_t size = 0;
char buffer[BUFFER_SIZE];
FILE *stream;
char filepath[100];
strcpy(buffer,"please enter a path!\n");
write(sd,buffer,BUFFER_SIZE);
int length = 0;
memset(filepath,'\0',sizeof(filepath));
length = read(sd,filepath,100);
if(length < 0){
printf("recv error!\n");
}
else
{
char filename[100] = {'\0'};
get_filename(filepath,filename);
printf("server: filename:\n%s",filename);
if( (stream=fopen(filename, "w")) == NULL){
printf("server:open file error!\n");
return;
}
while(1){/*读取文件并写入文件流*/
size = read(sd, buffer, BUFFER_SIZE);
printf("server:size:%d\n",size);
if(size <= 0){
break;
}
int write_len=fwrite(buffer, sizeof(char), size, stream);
}
printf("recv finished!\n");
fclose(stream);
}
}
int main(int argc, char *argv[]){
int socksd,sockcd;
struct sockaddr_in server,client;
pid_t pid;
if( (socksd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
printf("socket create error!\n");
return -1;
}
printf("socket create success!\n");
/*加入此代码是为了避免再次打开服务器程序出现bind error的错误*/
int on = 1;
int ret = setsockopt(socksd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(PORT);
if( (bind(socksd,(struct sockaddr*)&server,sizeof(server)) < 0))
{
printf("socket bind error!\n");
return -1;
}
printf("socket bind success!\n");
if( (listen(socksd,10)) < 0)
{
printf("socket listen error!\n");
return -1;
}
printf("socket listen success!\n");
printf("waiting...\n");
/*显示核是sigchld信号*/
if(signal(SIGCHLD, SIG_IGN) == SIG_ERR){
perror("signal error");
return EXIT_SUCCESS;
}
while(1){
socklen_t addr_len = sizeof(struct sockaddr);
if( (sockcd = accept(socksd, (struct sockaddr*)&client, &addr_len)) < 0)
{
//出错
continue;
}
printf("server:accept\n");
/*建立一个新进程来处理到来的连接*/
pid = fork();
if(pid == 0)
{
process_conn_server(sockcd);
close(socksd);/*在子进程中关闭服务器的监听*/
exit(0);
return 0;
}
else{
close(sockcd);/*在父进程中关闭客户端的监听*/
}
}
return 0;
}
编译时可以用命令行将前4个文件一起编译,第五个单独编译,然后运行,也可以使用Makefile文件编译:
Makefile文件内代码如下:
tcp_server tcp_client2:tcp_server.o tcp_client2.o connect.o errexit.o connectsock.o
gcc tcp_server.o -o tcp_server
gcc tcp_client2.o connect.o errexit.o connectsock.o -o tcp_client2
tcp_server.o:tcp_server.c
gcc -c tcp_server.c -o tcp_server.o
tcp_client2.o:tcp_client2.c
gcc -c tcp_client2.c -o tcp_client2.o
connect.o:connect.c
gcc -c connect.c -o connect.o
connectsock.o:connectsock.c
gcc -c connectsock.c -o connectsock.o
errexit.o:errexit.c
gcc -c errexit.c -o errexit.o
.PHONT:clean
clean:
rm -rf *.o
注:代码直接复制到Makefile文件中不能用,gcc、rm命令前面是一个制表符,不是空格!!!
运行结果
make编译过程如下:
服务器端程序运行结果如下:
客户端程序运行如下:
可以看出,1.png文件传输成功
Ps.有关Makefile的编写规则,可以参考如下博客: