以前我们讲的进程间的通信,是实现一台计算机上不同进程之间的通信。现在我们可以通过网络编程实现不同机器,甚至不同操作系统之间的通信。
计算机网络基础知识
这里不介绍关于网络的具体知识,关于tcp/ip的预备知识可参考谢希仁的《计算机网络》。
本文将通过一个实例实现linux与windows之间的文件传送,要求使用tcp协议。
以下函数的介绍是本人结合实例给出的定义,难免有疏漏,望谅解。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
描述:client端连接到server端,如果成功返回0,失败返回-1;
第一个参数指客户端的socekt,client_st
第二个参数指server端ip地址结构
第三个参数指ip地址长度
int socket(int domain, int type, int protocol);
描述:创建socket,如果创建成功,返回socket描述符
第一个参数一般填 AF_INET 意思是 IPv4 Internet protocols
第二个参数如果要用tcp协议 填SOCK_STREAM ;如果要用UDP协议填SOCK_DGRAM
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
描述:bind将socket与本地(server)IP地址绑定
第一个参数sockfd指刚创建还未与ip地址绑定的socket
第二个参数addr指ip地址结构
第三个参数addrlen指addr的数据长度
int listen(int sockfd, int backlog);
描述:server端listen 是否有来自client端的连接到达,如果有成功连接则返回0;如果失败错误则返回-1
第一个参数是socket描述符
第二个参数指最多能listen来自客户端的个数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
描述:如果有client的连接到达,accept返回一个client_st,这个socekt是新创建,原来处在listening state的socket不受影响
第一个参数指listenng socket(listen_st)
第二个参数指client_addr,这是通过指针返回的client端的ip结构地址
第三个参数是client端地址的长度,这也是一个指针返回的值
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
描述:socket发送数据,前提是socket处在连接状态,如果成功发送,返回发送的字节数;失败则返回-1;
第一个参数:socket
第四个参数:一般设为0
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
描述:socket接收数据,前提是socket处在连接状态,如果成功接收,返回接收的字节数;失败返回-1;
第一个参数:socket
第四个参数:一般设为0
代码中有详细的注释
/*
============================================================================
Name : server.c
Author : Allen
Version :
Copyright : Your copyright notice
Description : Hello World in C, Ansi-style
============================================================================
*/
#include
#include
#include"pub.h"
int main(int arg,char *args[])
{
if(arg<2)
{
printf("usage:server port\n");
return EXIT_FAILURE;
}
int port=atoi(args[1]);
if (port == 0)
{
printf("port %d is invalid\n", port);
return EXIT_FAILURE;
}
printf("recv begin\n");
if((recv_work(port))==1)
printf("recv success\n");
else
printf("recv fail\n");
return EXIT_SUCCESS;
}
/*
* client.c
*
* Created on: 2016年10月20日
* Author: Administrator
*/
#include
#include
#include"pub.h"
int main(int arg, char*args[])
{
if (arg < 4)
{
printf("usage:client hostname port filepath");
return EXIT_FAILURE;
}
int port = atoi(args[2]);
if (port == 0) //如果端口号为0,main函数退出
{
printf("port %d is invalid\n", port);
return EXIT_FAILURE;
}
printf("%s send begin\n", args[3]);
if ((send_work(args[1], port, args[3])) == 1)
printf("send success\n");
else
printf("send_work failed\n");
return EXIT_SUCCESS;
}
/*
* pub.h
*
* Created on: 2016年10月20日
* Author: Administrator
*/
#ifndef __PUB_H
#define __PUB_H
int send_work(const char*hostname,int port,const char*filename);
int recv_work(int port);
#endif
/*
* pub.c
*
* Created on: 2016年10月20日
* Author: Administrator
*/
//如果是windows操作系统就包含WinSock2.h头文件
#ifdef WIN
#include
//如果不是
#else
#include
#include
#include
#include
#include
#include
#define SOCKET int //因为windows下的socket是特殊的SOCKET类型
#endif
#include
#include "pub.h"
#define SOCKET int
#include
#define MAXBUFSIZE 262144
//getfilename将要要发送的文件路径解析出其中的文件名称C:\myfiletransfer\src\TCP.PNG
//就解析出TCP.PNG
void getfilename(const char*filename, char *name)
{
int len = strlen(filename);
int i = 0;
for (i = (len - 1); i >= 0; i--)
{
if ((filename[i] == '/') || (filename[i] == '\\'))
{
break;
}
}
strcpy(name, &filename[i + 1]);
return;
}
//windows下使用要socket要进行初始化,linux则不需要
SOCKET socket_init()
{
//如果是windows,执行如下代码
#ifdef WIN
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return -1;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
#endif
return 0;
}
//client端connect
SOCKET socket_connect(const char*hostname, int port)//连接到指定主机和端口号
{
if (socket_init() == -1)
return 0;
SOCKET client_st = socket(AF_INET, SOCK_STREAM, 0); //初始化客户端socket为tcp
struct sockaddr_in addr; //目的主机地址
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);//指定port为要连接的端口号
addr.sin_addr.s_addr = inet_addr(hostname);//指定hostname为要连接的IP地址
if ((connect(client_st, (struct sockaddr*) &addr, sizeof(addr)) == -1))
{
printf("connect failed %s\n", strerror(errno));
return 0;
}
return client_st;//连接成功,则返回client端的socket描述符
}
//server端bind listen
SOCKET socket_create(int port)//在指定的端口号建立server端的socket
{
if (socket_init() == -1)
return 0;
SOCKET st = socket(AF_INET, SOCK_STREAM, 0);//建立TCP socket
if (st == 0)
{
printf("socket failed %s\n", strerror(errno));
return 0;
}
#ifdef WIN
const char on = 0;
#else
int on = 0;
#endif
if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
{
printf("setsockopt failed %s\n", strerror(errno));
return 0;
}
struct sockaddr_in addr;//定义一个IP地址结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;//将addr结构的属性定位为TCP/IP地址
addr.sin_port = htons(port);//将本地字节顺序转化为网络字节顺序。
addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY代表这个server上所有的地址
//server端socket 与 server上的ip地址绑定
if ((bind(st, (const struct sockaddr *) &addr, sizeof(addr))) == -1)
{
printf("bind failed %s\n", strerror(errno));
return 0;
}
if ((listen(st, 20)) == -1)
{
printf("listen failed %s\n", strerror(errno));
return 0;
}
printf("listen %d success\n", port);
return st; //server端的socket建立成功,返回server端的socket描述符
}
//server端accept 返回连接到server的client的socket描述符
SOCKET socket_accept(SOCKET listen_st)//server端socket开始accept的函数
{
struct sockaddr_in client_addr;
#ifdef WIN
int len = 0;
#else
unsigned int len = 1;
#endif
len = sizeof(client_addr);
memset(&client_addr, 0, sizeof(client_addr));
SOCKET client_st = accept(listen_st, (struct sockaddr *) &client_addr,
&len);//accept阻塞,直到有client连接到server才返回
if (client_st == -1)
{
printf("accept failed %s\n", strerror(errno));
return 0;
} else
{
printf("accept by %s\n", inet_ntoa(client_addr.sin_addr));
return client_st;//有client连接到server,则返回client的socket描述符
}
}
//client端向server端发送文件 向指定的ip 指定的端口号发送指定的文件
int send_work(const char*hostname, int port, const char*filename)
{
SOCKET st = socket_connect(hostname, port);//连接到hostname指定的的ip地址和port指定的端口号
if (st == 0)
{
return 0;
}
FILE*fp = fopen(filename, "rb");//以只读的方式打开filename rb是windows用于读取二进制文件,linux下与r相同
if (fp == NULL)
{
printf("fopen failed %s\n", strerror(errno));
return 0;
}
char *buf = malloc(MAXBUFSIZE);//申请一块缓存区,存放接收到的文件内容
memset(buf, 0, MAXBUFSIZE);
getfilename(filename, buf); //从完整的路径名解析出的文件名称
int rc = send(st, buf, strlen(buf), 0);//client端第一次给server端发送的是解析得到的文件名称
if (rc <= 0)
{
if (rc < 0)
printf("send failed %s\n", strerror(errno));
else
printf("socket disconnect\n");
} else
{
memset(buf, 0, MAXBUFSIZE);
rc = recv(st, buf, MAXBUFSIZE, 0);//接受来自server端的回复
if (rc <= 0)
{
if (rc < 0)
printf("recv failed %s\n", strerror(errno));
else
printf("socket disconnect");
} else
{
if (strncmp(buf, "OK", 2) == 0)//如果server端给client回复了OK,代表server认可,client可以发送数据
{
while (1)
{
memset(buf, 0, MAXBUFSIZE);
rc = fread(buf, 1, MAXBUFSIZE, fp);//循环读取文件,直到读到文件末尾,循环break
if (rc <= 0)
{
if (rc < 0)
printf("fread failed %s\n", strerror(errno));
break;
} else
{
rc=send(st, buf, rc, 0);//将从文件读取的数据,通过socket发送到server端,其中rc为读取到的数据的大小
if (rc <= 0)
{
if (rc < 0)
printf("send failed %s\n", strerror(errno));
else
printf("socket disconnect");
break;
}
}
}
}
}
}
fclose(fp);//关闭文件
free(buf);//释放buf
#ifdef WIN
closesocket(st);
WSACleanup();
#else
close(st);
#endif
return 1;
}
int recv_work(int port)//server端socket在port指定的端口listen,接受来自client发送的文件
{
SOCKET listen_st = socket_create(port);//建立server端socket,在port指定的端口号listen
if (listen_st == 0)
{
return 0;
}
SOCKET st = socket_accept(listen_st);//如果有client连接到达,socket_accept函数返回client的socket描述符
if (st == 0)
{
return 0;
}
char *buf = malloc(MAXBUFSIZE);
if (buf == NULL)
{
return 0;
}
FILE *fp = NULL;
memset(buf, 0, MAXBUFSIZE);
int rc = recv(st, buf, MAXBUFSIZE, 0);//接受client端第一次发送的数据,即文件名称
if (rc <= 0)
{
if (rc < 0)
{
printf("recv failed %s\n", strerror(errno));
} else
{
printf("socket disconnect\n");
}
} else
{
printf("recving %s\n", buf);
fp = fopen(buf, "wb");//以只写的方式打开buf指定文件名,若不存在,新建一个名为buf指定的文件名
if (fp == NULL)
{
printf("fopen failed %s\n", strerror(errno));
} else
{
memset(buf, 0, MAXBUFSIZE);
strcpy(buf, "OK");
rc = send(st, buf, strlen(buf), 0);//如果已接收到client端第一次发来的文件名,则server端要给client端发送OK去人
if (rc <= 0)
{
if (rc < 0)
printf("send failed %s\n", strerror(errno));
else
printf("socket disconnect");
}
while (1)
{
memset(buf, 0, MAXBUFSIZE);
rc = recv(st, buf, MAXBUFSIZE, 0);//循环开始接受数据,数据为文件内容
if (rc <= 0)
{
if (rc < 0)
printf("recv failed %s\n", strerror(errno));
else
printf("socket disconnect\n");
break;
} else
{
fwrite(buf, 1, rc, fp);//将接受到的数据,写入fp指定的文件,一条记录为1个字节,一次写rc条记录
}
}
}
}
if (fp)
fclose(fp);
free(buf);
#ifdef WIN
closesocket(st);
closesocket(listen_st);
WSACleanup();
#else
close(st);
close(listen_st);
#endif
return 1;
}
下面分别提供了linux下的makefile以及windows下的makefile
.SUFFIXES: .c .o
CC=gcc
SRCS1=client.c\
pub.c
SRCS2=server.c\
pub.c
OBJS1=$(SRCS1:.c=.o)
OBJS2=$(SRCS2:.c=.o)
EXEC1=client
EXEC2=server
all: $(OBJS1) $(OBJS2)
$(CC) -o $(EXEC1) $(OBJS1)
$(CC) -o $(EXEC2) $(OBJS2)
@echo '-------------ok--------------'
.c.o:
$(CC) -Wall -g -o $@ -c $<
clean:
rm -f $(OBJS1)
rm -f $(OBJS2)
rm -f core*
.SUFFIXES: .c .o
CC=gcc
SERVERSRCS=server.c\
pub.c
CLIENTSRCS=client.c\
pub.c
SERVEROBJS=$(SERVERSRCS:.c=.o)
CLIENTOBJS=$(CLIENTSRCS:.c=.o)
SERVEREXEC=server.exe
CLIENTEXEC=client.exe
all:$(SERVEROBJS) $(CLIENTOBJS)
$(CC) -static -o $(SERVEREXEC) $(SERVEROBJS) -lWs2_32
$(CC) -static -o $(CLIENTEXEC) $(CLIENTOBJS) -lWs2_32
@echo '-------------ok--------------'
.c.o:
$(CC) -Wall -DWIN -o $@ -c $<
clean:
rm -f $(SERVEROBJS)
rm -f $(CLIENTOBJS)
rm -f core*
gcc的小知识点:
-lWs2_32 , 代表要用Ws2_32.lib这个库
gcc编译选项,-D 代表定义一个宏,等同于在c语言当中定义 #defind WIN