/*
文档笔记:
1.
TCP通信结构图
服务器 客户端
|
v
创建套接字:socket()
|
v
绑定套接字:bind()
| |
v v
监听:listen() 创建套接字:socket()
| |
v v
接受连接:accept() <--- 连接服务器:connect()
| |
v v
收消息:read()/recv() <--- 发消息:write()/send()
| |
v v
发消息:write()/send() ---> 收消息:read()/recv()
三次握手:第一次是客户端请求连接,第二次是服务器授予连接,第三次是客户端发送确认连接给服务器。
2.TCP/IP协议:(Transmission Control Protocol/Internet Protocol)传输控制协议/因特网互联协议,又名网络通讯协议。
通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,知道所有的数据安全正确地传输到目的地,
而IP是给因特网的每台电脑规定一个IP地址。
3.socket套接字,指的是在网络通信以前建立的通信接口。进行网络连接以前,需要向系统注册申请一个新的socket,然后使用这个socket进行网络连接。
4.int socket(int domain,int type,int protocol);
返回值:成功时,返回一个小的非负整数值,与文件描述符类似,出错时,返回-1。
参数: domain: 协议族 a.AF_INET //IPv4 b.AF_INET6 //IPv6
type: socket类型
a.SOCK_STREAM //TCP
b.SOCK_DGRAM //UDP
c.SOCK_RAW //IP
protocol:所使用的协议。一般是等于0。
type: socket类型 a.SOCK_DGRAM //TCP b.SOCK_DGRAM //UDP c.SOCK_RAW //IP protocol:所使用的协议。一般是等于0。
5.int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen) 绑定端口:当socket函数返回一个描述符时,只是存在于其协议族的空间中,
并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),
bind函数可以将一组固定的地址绑定到sockfd上。 返回值:0 -- 成功,-1 -- 出错。
参数: sockfd:是socket返回的描述符。 my_addr:指定了想要绑定的IP和端口号,均要使用网络字节序。
addrlen:是前面struct sockaddr(与sockaddr_in等价)的长度。
struct sockadr_in{
unsigned short int sin_family; //协议族
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //Internet地址
unsigned char sin_zero[8]; //未使用的字段,填充为0
};
struct in_addr{
unsigned long s_addr;
};
a. 利用u_long htonl(u_long hostlong);将主机字节序转换为TCP/IP网络字节序.
b. 利用u_short htons(u_short hostshort);将主机字节序转换为TCP/IP网络字节序
inet_addr()是将一个点分10进制的ip地址(如192.168.0.1)转换为上述结构中需要的32位ip地址(0xC0A80001)
6.int listen(int sockfd, int count); 监听:等待用户的连接。
返回值:0 -- 成功,-1 -- 出错。
参数: sockfd: 创建的套接字描述符。 count: 最大连接数。
7.int connect(int sockfd, struct sockaddr *addr, socklen_t addlen );
连接:客户端通过connect函数实现和服务器建立连接。 返回值:0 -- 成功,-1 -- 出错。
参数: sockfd: 客户端的socket描述符。
addr: 服务器地址。
addlen: socket地址的长度。
8.int accept(int sockfd, struct sockaddr *addr, socklen_t *add_len);
连接:accept函数成功返回,那么服务器与客户端成功连接,此时服务器即可通过accept函数返回的连接套接字和客户端进行通信。
accept函数默认是阻塞进程,直到有客户端连接成功,他会返回一个连接套接字。
参数: sockfd: 服务器套接字描述符。 addr: 用来返回客户端的协议地址。 add_len: 协议地址的长度。
9.size_t send(int sockfd, const char *buf, size_t len, int flag);
返回值: >0 – 成功拷贝至发送缓冲区的字节数(可能小于len),
-1 – 出错,并置错误号errno.
参数: sockfd: 发送端套接字描述符(非监听描述符),也就是说在服务器端就是连接套接字,在客户端就是他本省那个套接字。
buf: 要发送数据的缓存。
len: 实际要发送的数据长度。
flag: 指定的协议,一般设置为0。
10.size_t recv(int sockfd, char *buf, size_t len, int flag);
返回值:成功时,返回拷贝的字节数,失败返回-1。
参数: sockfd: 发送端套接字描述符(非监听描述符),也就是说在服务器端就是连接套接字,在客户端就是他本省那个套接字。
buf: 指定缓存区地址,用于存储接收数据。
len: 指定的用于接收数据的缓冲区长度。
flag: 指定的协议,一般设置为0。
例子:(客户端像服务器下载文件)
*/
/* 主要是建立一个服务器,然后发送一个目录给客户端,客户端发送要下载的文件名,
然后将此文件发给客户端。 执行的时候:gcc server.c -o ser
./ser /home/robert/file/ (主要是路径后面加个/)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void send_dir(char * dirname);
void send_file(char *filename,char *download_file);
int cfd;
#define TCP_BUF_LEN 20
void usage(char *command)
{
printf("usage: %s file direction\n",command);
exit(0);
}
int main(int argc,char *argv[])
{
int nZero = 0;
if(argc !=2)
{
usage(argv[0]);
}
//创建一个套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd < 0)
{
printf("creat socket failed!\n"); return -1;
}
//绑定套接字
struct sockaddr_in addr_srv;
//将端口,协议,IP地址赋值然后绑定到套接字上
addr_srv.sin_family = AF_INET;
addr_srv.sin_port = htons(6500);
addr_srv.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY本地IP地址。
nZero = TCP_BUF_LEN;
setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (char*)&nZero, sizeof((char*)&nZero));
setsockopt(sfd, SOL_SOCKET, SO_RCVBUF, (char*)&nZero, sizeof((char*)&nZero));
int ret = bind(sfd,(struct sockaddr*)&addr_srv,sizeof(addr_srv));
if(ret < 0)
{
printf("bind socket failed!\n");
return -1;
}
//监听
ret = listen(sfd,10);
if(ret < 0)
{
printf("listen error!\n");
return -1;
}
struct sockaddr_in addr_cli; //接收客户端的协议信息
socklen_t add_len = sizeof(addr_cli);//char a[30] = "abcd",sizeof(a)=30,strlen(a)=4.
char msg[256] = {0}; //为了让服务器一直运行
while(1)
{
//连接,获取客户端的协议地址
cfd = accept(sfd,(struct sockaddr *)&addr_cli,&add_len);
if(cfd < 0)
{
printf("connect failed!\n");
return -1;
}
printf("send file dir!\n");
send_dir(argv[1]);
recv(cfd,msg,sizeof(msg),0);
printf("IP:%s:%s\n",inet_ntoa(addr_cli.sin_addr),msg);
send_file(argv[1],msg);
printf("11111111111111111111\n");
}
close(cfd);
close(sfd);
return 0;
}
void send_dir(char * dirname)
{
DIR *dir = opendir(dirname);
if(dir == NULL)
{
printf("open file's dir failed!\n");
exit(-1);
}
struct dirent *ptr;
char buf[256] = {0}; //存储文件名
while(ptr=readdir(dir))
{
if(ptr->d_name[0] != '.')//因为一般文件前面都没有点,有点都是缓存文件
{
strcpy(buf,ptr->d_name);
strcat(buf,"\n");//为了使得一行一个文件名 这样好看点
send(cfd,buf,strlen(buf),0);//发送文件名
}
usleep(500);
}
memset(buf,0,sizeof(buf));
usleep(500);
strcpy(buf,"$");//$是一个标志,表明文件名称发送完毕。
send(cfd,buf,strlen(buf),0);
closedir(dir);
}
void send_file(char *filename,char *download_file)
{
DIR *dir = opendir(filename);
if(dir == NULL)
{ printf("open dir error!\n");
exit(-1); //因为这个函数是没有返回值的,所以要用exit(-1),而不能用return(-1).
//exit(-1)和return(-1)基本上是一样的,就在这个有点区别。
}
struct dirent *ptr;
char buf2[200]= {0}; //发送文件内容缓冲区
char cBuf[1024];
int j;
for(j=0;j<1000;j++)
{
cBuf[j]='5';
}
while(ptr = readdir(dir))
{
if(strcmp(ptr->d_name,download_file) == 0)
//字符数组不能直接比较,必须通过strcmp()函数比较
{
strcpy(buf2,filename);
//字符数组不能直接赋值,必须通过strcpy()函数进行赋值
strcat(buf2,ptr->d_name);
//在字符数组后面加字符,需要通过strcat()函数
FILE *fp = fopen(buf2,"r");
if(fp == NULL)
{
printf("open file error server!\n");
exit(-1);
}
memset(buf2,0,sizeof(buf2));//将buf2全部置0
printf("start to send file!\n");
int i;
while(!feof(fp))
{
fread(buf2,1,20,fp);
printf("%s",buf2);
printf("strlen(buf2)=%d",strlen(buf2));
if(strlen(buf2)<20)
{
memcpy(buf2+10,cBuf,20-strlen(buf2));
}
send(cfd,buf2,strlen(buf2),0);
//printf("server--->%d",i++);
memset(buf2,0,sizeof(buf2));
//usleep(1000);
}
printf("file have been send finshed!\n");
usleep(500);
strcpy(buf2,"$$$$$$$$$$$$$$$$$$$$");
send(cfd,buf2,strlen(buf2),0);
fclose(fp);
}
}
closedir(dir);
//printf("2222222222222222222\n");
}
/*
这个是一个客户端,主要功能是连接服务器,到服务器上下载文件。
执行的时候:gcc client.c -o cli
./cli 192.168.26.32(服务器的IP)
*/
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define TCP_BUF_LEN 20
void usage(char *command)
{
printf("usage: %s server ip\n",command);
exit(0);
}
int main(int argc,char *argv[])
{
int nZero = 0;
if(argc !=2)
{
usage(argv[0]);
}
//创建套接字
int cfd;
cfd = socket(AF_INET,SOCK_STREAM,0);
if(cfd<0)
{
printf("create socket failed!\n");
exit(-1);
}
//连接服务器
struct sockaddr_in addrcli;
addrcli.sin_family = AF_INET; //通信协议
addrcli.sin_port = htons(6500); //通信端口
addrcli.sin_addr.s_addr = inet_addr(argv[1]); //服务器ip
nZero = TCP_BUF_LEN;
setsockopt(cfd, SOL_SOCKET, SO_SNDBUF, (char*)&nZero, sizeof((char*)&nZero));
setsockopt(cfd, SOL_SOCKET, SO_RCVBUF, (char*)&nZero, sizeof((char*)&nZero));
int ret = connect(cfd,(struct sockaddr *)&addrcli,sizeof(addrcli));
if(ret<0)
{
printf("connect server error!\n");
exit(-1);
}
//接收文件名称
char msg[100]={0};
while(recv(cfd,msg,1,0))
{
if(strcmp(msg,"$")==0)
{
break;
}
printf("%s",msg);
}
memset(msg,0,sizeof(msg));
//下载需要下载的文件
printf("Please input you want to download file's name:\n");
scanf("%s",msg);
send(cfd,msg,strlen(msg),0);
char c[256]={0};
strcpy(c,"./");
strcat(c,msg);
FILE *fp=fopen(c,"w");
if(fp == NULL)
{
printf("open file error!\n");
exit(-1);
}
memset(msg,0,100);
printf("Start to recv file:\n");
int i;
int tmp;
int val;
//while(recv(cfd,msg,sizeof(*msg),0))
while(tmp = recv(cfd,msg,20,0))
{
//printf(" in tmp=%d\n",tmp);
//msg[19]='\0';
//printf("%s",msg);
//for(i=0;i<30;i++)
//{
// printf("%c",msg[i]);
//}
//printf("\n");
//msg[20]='\0';
printf("aaaaaaaaa\n");
//printf("%s",msg);
//printf("bbbbbbbbb\n");
if(strcmp(msg,"$$$$$$$$$$$$$$$$$$$$")==0) //接收完毕
{
break;
}
val = fwrite(msg,strlen(msg),1,fp);
//printf("val =%d\n",val);
//printf("cccccccccccc\n");
//printf("client--->%d",i++);
//printf("dddddddddddd\n");
}
//printf("out tmp=%d\n",tmp);
printf("Recv file finish!\n");
fclose(fp);
close(cfd);
return 0;
}