Linux网络编程及文件操作

了解和熟悉Linux 环境下基于套接口的通信方式。本地进程间通信方
式局限在单一计算机内,基于套接口的通讯方式不仅可以实现单机内的进
程间通信,还可以实现不同计算机进程之间的通信。
文件操作是Linux 系统中最常见的操作之一,熟悉Linux 的文件系统及
其操作的相关系统调用,了解和掌握基于文件描述符和基于数据流的文件
I/O 操作方式。

tcp连接

TCP协议:是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。

关键词:三次握手,可靠,基于字节流。

例子1

服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*使用面向连接的套接口实现通信*/
/*套接口方式不仅可以实现单机内进程间通信,还可以实现不同计算机进程之间通信*/
/*先运行tcp-server程序,端口号作为参数,实现server程序的监听。*/
/*再运行tcp-client程序,以server所在的“ip地址127.0.0.1”或“主机名(localhost)”为第一参数,相同的端口号作为第二参数连接server*/

/*tcp-server.c */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAXSIZE 1024 /*定义数据缓冲区大小*/

int main(int argc, char *argv[])
{
int sockfd,new_fd; /*定义存放套接口描述符的变量 */
struct sockaddr_in server_addr; /*定义服务器端套接口数据结构server_addr */
struct sockaddr_in client_addr; /*定义客户端套接口数据结构client_addr */
int sin_size,portnumber;
char buf[MAXSIZE]; /*定义发送数据缓冲区*/
if(argc!=2)
{
fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
if((portnumber=atoi(argv[1]))<0)
{ /*获得命令行的第二个参数--端口号,atoi()把字符串转换成整型数*/
fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
exit(1);
}
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)/*服务器端开始建立socket描述符*/
{
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/*服务器端填充 sockaddr结构*/
bzero(&server_addr,sizeof(struct sockaddr_in)); /*先将套接口地址数据结构清零*/
server_addr.sin_family=AF_INET;/*设为TCP/IP地址族*/
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*设置本机地址并从主机字节序转换为网络字节序*/
server_addr.sin_port=htons(portnumber);/*设置端口号并从主机字节序转换为网络字节序*/
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)/*调用bind函数绑定指定的端口号和ip地址到服务器创建的套接口*/
{
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
exit(1);
}
if(listen(sockfd,5)==-1) /*端口绑定成功,监听sockfd描述符,设置同时处理的最大连接请求数为5 */
{
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
while(1) /*服务器阻塞,等待接收连接请求,直到客户程序发送连接请求*/
{
sin_size=sizeof(struct sockaddr_in);
if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1) /*调用accept接受一个连接请求并返回一个新的套接口描述符*/
{
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
exit(1);
}
fprintf(stderr,"Server get connection from %s\n",(inet_ntoa(client_addr.sin_addr))); /*TCP连接已建立,打印申请连接的客户机的IP地址,IP地址从网络字节序转换为十进制数*/
printf("Connected successful, please input the masage[<1024 bytes]:\n"); /*提示用户输入将要发送的数据,长度小于缓冲区的长度,即1024字节*/

if(fgets(buf, sizeof(buf), stdin) != buf) /*从标准输入即键盘输入的数据存放在buf缓冲区*/
{
printf("fgets error!\n");
exit(1);
}
if(write(new_fd,buf,strlen(buf))==-1) /*调用write发送数据*/
{
fprintf(stderr,"Write Error:%s\n",strerror(errno));
exit(1);
}
close(new_fd); /*本次通信已结束,关闭客户端的套接口,并循环下一次等待*/
}
close(sockfd); /*服务器进程结束,关闭服务器端套接口*/
exit(0);
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*使用面向连接的套接口实现通信*/
/*套接口方式不仅可以实现单机内进程间通信,还可以实现不同计算机进程之间通信*/
/*先运行tcp-server程序,端口号作为参数,实现server程序的监听。*/
/*再运行tcp-client程序,以server所在的“ip地址127.0.0.1”或“主机名(localhost)”为第一参数,相同的端口号作为第二参数连接server*/

/*tcp-client.c */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr; /*定义服务器端套接口数据结构server_addr */
struct hostent *host;/*定义一个hostent结构的指针 */
int portnumber,nbytes;
if(argc!=3)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL)/*通过域名或主机名得到包含地址的hostent指针 */
{ /*获得命令行的第二个参数-主机名*/
fprintf(stderr,"Gethostname error\n");
exit(1);
}
if((portnumber=atoi(argv[2]))<0)
{ /*获得命令行的第三个参数--端口号,atoi()把字符串转换成整型数*/
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
/* 客户程序开始建立 sockfd描述符 */
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/*客户程序填充服务端的资料*/
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr=*((struct in_addr *)host->h_addr);
/*客户程序发起连接请求*/
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
{
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
/*连接成功,调用read读取服务器发送来的数据*/
if((nbytes=read(sockfd,buffer,1024))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
exit(1);
}
buffer[nbytes]='\0';
printf("I have received:%s\n",buffer); /*输出接收到的数据*/
close(sockfd); /*结束通信*/
exit(0);
}

例子2

服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*先运行tcp-server程序,再运行tcp-client程序*/


#include <sys/types.h>
#include <sys/socket.h> // 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h> // 包含AF_INET相关结构
#include <arpa/inet.h> // 包含AF_INET相关操作的函数
#include <unistd.h>
#define PORT 3339
int main()
{
char sendbuf[256]="OK";
char buf[256];
int s_fd, c_fd; // 服务器和客户套接字标识符
int s_len, c_len; // 服务器和客户消息长度
struct sockaddr_in s_addr; // 服务器套接字地址
struct sockaddr_in c_addr; // 客户套接字地址
s_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
s_addr.sin_family = AF_INET; // 定义服务器套接字地址中的地址域为IPv4
s_addr.sin_addr.s_addr=htonl(INADDR_ANY); // 定义套接字地址
s_addr.sin_port = htons(PORT); // 定义服务器套接字端口
s_len = sizeof(s_addr);
bind(s_fd, (struct sockaddr *) &s_addr, s_len); // 绑定套接字与设置的端口号
listen(s_fd, 10); //监听,最大连接请求设为10
printf("请稍候,等待客户端发送数据\n");
c_len = sizeof(c_addr); //接收客户端连接请求
c_fd = accept(s_fd,(struct sockaddr *) &c_addr,(socklen_t *__restrict) &c_len);
while (1) {
if(recv(c_fd,buf,256,0)>0) //接收消息recv(c_fd,buf,256,0)>0
{
//read(c_fd,buf,256,0)
//buf[sizeof(buf)+1]='\0';
printf("收到客户端消息:\n %s\n",buf);//输出到终端
send(c_fd,sendbuf,sizeof(sendbuf),0);//回复消息
}
}
close(c_fd); // 关闭连接
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*先运行tcp-server程序,再运行tcp-client程序*/


#include <sys/types.h>
#include <sys/socket.h> // 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h> // 包含AF_INET相关结构
#include <arpa/inet.h> // 包含AF_INET相关操作的函数
#include <unistd.h>
#define PORT 3339
int main() {
int sockfd; // 客户套接字标识符
int len; // 客户消息长度
struct sockaddr_in addr; // 客户套接字地址
int newsockfd;
char buf[256]="come on!";//要发送的消息
int len2;
char rebuf[256];
sockfd = socket(AF_INET,SOCK_STREAM, 0); // 创建套接字
addr.sin_family = AF_INET; // 客户端套接字地址中的域
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port = htons(PORT); // 客户端套接字端口
len = sizeof(addr);
newsockfd = connect(sockfd, (struct sockaddr *) &addr, len); //发送连接服务器的请求
if (newsockfd == -1) {
perror("连接失败");
return 1;
}
len2=sizeof(buf);
while(1){
printf("请输入要发送的数据:");
scanf("%s",buf);
send(sockfd,buf,len2,0); //发送消息
if(recv(sockfd,rebuf,256,0)>0)//接收新消息
{
//rebuf[sizeof(rebuf)+1]='\0';
printf("收到服务器消息:\n%s\n",rebuf);//输出到终端
}
}
close(sockfd); // 关闭连接
return 0;
}

文件I/O操作

例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*基于文件描述符的操作*/
/*程序中创建出的文件,可使用“od -td1 -tc -Ad 文件名”方式查看其内容结构 */
/*-td1选项表示将文件中的字节以十进制的形式列出来,每组一个字节。
-tc选项表示将文件中的ASCII码以字符形式列出来。
输出结果最左边的一列是文件中的地址,-Ad选项要求以十进制显示文件中的地址。*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define FILENAME "test" /*要进行操作的文件*/
#define FLAGS O_WRONLY | O_CREAT | O_TRUNC
/*定义参数FLAGS:以读写方式打开文件,向文件添加内容时从文件尾开始写*/
#define MODE 0600

int main(void)
{
char buf1[ ] = {"abcdefghij"}; /*缓冲区1,长度为10*/
char buf2[ ] = {"1234567890"}; /*缓冲区2,长度为10*/
int fd; /*文件描述符*/
int count;
const char *pathname = FILENAME; /*指向需要进行操作的文件的路径名*/
if((fd=open(pathname,FLAGS,MODE))==-1) /*调用open函数打开文件*/
{
printf("error,open file failed!\n");
exit(1); /*出错退出*/
}
count = strlen(buf1); /*缓冲区1的长度*/
if(write(fd,buf1,count)!=count) /*调用write函数将缓冲区1的数据写入文件*/
{
printf("error,write file failed!\n");
exit(1); /*写出错,退出*/
}
system("cat test");//使用cat命令显示文件内容
printf("\n");

if(lseek(fd,5,SEEK_SET)==-1)
/*调用lseek函数定位文件,偏移量为5,从文件开头计算偏移值*/
{
printf("error,lseek failed!\n");
exit(1); /*出错退出*/
}
count = strlen(buf2); /*缓冲区2的长度*/
if(write(fd,buf2,count)!=count) /*调用write函数将缓冲区2的数据写入文件*/
{
printf("error,write file failed!\n");
exit(1); /*写出错,退出*/
}
system("cat test");//使用cat命令显示文件内容
printf("\n");
return 0;
}

例子2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*基于流的I/O操作--打开关闭文件流的操作*/
/*基于流的I/O操作比基于文件描述符的I/O操作更简单方便一些*/
/*参阅《LinuxC编程一站式学习》第25章C标准库第2节标准IO库函数的内容*/

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void)
{
FILE *fp;
int fd;

if( (fp = fopen("hello.txt", "w")) == NULL)
{ /*以只写方式打开文件,并清空文件。若没有此文件,则创建它。路径为当前目录下,也可使用绝对路径。打开成功返回文件指针。--基于流的方式*/
printf ("fail to open 1!\n");
exit(1); /*出错退出*/
}
fprintf(fp, "Hello! I like Linux C program!\n");
/*向该流输出一段信息,这段信息会保存到打开的文件上,形成文件文件*/
fclose(fp); /*操作完毕,关闭流*/

if( (fd = open("hello.txt", O_RDWR)) == -1)
{ /*以读写的方式打开文件--基于文件描述符的方式*/
printf ("fail to open!\n");
exit(1); /*出错退出*/
}

if((fp = fdopen(fd, "a+")) == NULL)
{ /*在打开的文件上打开一个流,基于已存在的文件描述符打开流,并从文件尾开始读写。*/
/*其中w代表write,r代表read,a代表append,+代表更新*/
printf ("fail to open stream!\n");
exit(1); /*出错退出*/
}
fprintf(fp, "I am doing Linux C programs!\n");
/*向该流输出一段信息,这段信息会保存到打开的文件上*/
fclose(fp); /*关闭流,文件也被关闭*/
system("cat hello.txt");//使用cat命令显示文件内容
return 0;
}

例子3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/*流文件指针位置的定位操作*/
/*程序中创建出的文件非纯文本文件,可使用“od -td1 -tc -Ad 文件名”方式查看其内容 */
/*-td1选项表示将文件中的字节以十进制的形式列出来,每组一个字节。
-tc选项表示将文件中的ASCII码以字符形式列出来。
输出结果最左边的一列是文件中的地址,-Ad选项要求以十进制显示文件中的地址。*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

struct stu
{
char name[10];
int age;
};

int main(void)
{
struct stu mystu[3]={{"Jim",14},{"Jam",15},{"Lily",19}};
struct stu mystuout;
FILE *fp;
extern int errno;
char file[]="record.txt";
int i,j;
long k;
fpos_t pos1,pos2;

fp=fopen(file,"w");
if(fp==NULL)
{
printf("cant't open file %s.\n",file);
printf("errno:%d\n",errno);
printf("ERR :%s\n",strerror(errno));
return(1);
}
else
{
printf("%s was opened.\n",file);
}

i=fwrite(mystu,sizeof(struct stu),3,fp);//创建的文件内容为二进制文件,非纯文本文件.
printf("%d students was written.\n",i);
fclose(fp);

/*以下为按指定要求读出记录*/

fp=fopen(file,"r");
if(fp==NULL)
{
printf("cant't open file %s.\n",file);
printf("errno:%d\n",errno);
printf("ERR :%s\n",strerror(errno));
return(1);
}


k=ftell(fp);//ftell函数可得到当前文件指针位置
printf("当前指针位置为%ld .\n",k);

fseek(fp,1*sizeof(struct stu),SEEK_SET);//从文件开始(SEEK_SET)移动指针至1个结构体的偏移量

fgetpos(fp,&pos1);//另外一种得到当前文件指针位置的方法
printf("移动指针后的当前指针位置为%f .\n",(float)pos1.__pos);

j=fread(&mystuout,sizeof(struct stu),1,fp);//从文件流中读1个结构体的长度的内容至mystuout
printf("%d students was read.\n",j);
printf("NAME:%s\tAGE:%d\n",mystuout.name,mystuout.age);

k=ftell(fp);//得到当前文件指针位置
printf("读出记录后的当前指针位置为%ld .\n",k);

j=fread(&mystuout,sizeof(struct stu),1,fp);
printf("%d students was read.\n",j);
printf("NAME:%s\tAGE:%d\n",mystuout.name,mystuout.age);

pos2.__pos=(long)(1*sizeof(struct stu));//设置移动量为一个结构体
fsetpos(fp,&pos2);//另外一种移动文件指针位置的方法

k=ftell(fp);
printf("再次移动指针后的当前指针位置为%ld .\n",k);

j=fread(&mystuout,sizeof(struct stu),1,fp);
printf("%d students was read.\n",j);
printf("NAME:%s\tAGE:%d\n",mystuout.name,mystuout.age);

k=ftell(fp);
printf("再次读记录后的当前指针位置为%ld .\n",k);

fclose(fp);

}

例子4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*一个简单的员工档案管理系统,可以实现简单的员工资料增加、删除及查询*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ARFILE "./usr.ar"//指定档案文件的路径名称

struct arstruct//员工资料结构
{
char name[10];
int age;
char tele[21];
};


/*删除员工函数==================================*/
void removeuser()
{
char name[10];
struct arstruct ar;
FILE *fp;
FILE *fpn;
if((fpn = fopen("./tmpfile","w")) == NULL)
{
return;
}
if((fp = fopen(ARFILE,"r")) == NULL)
{
return;
}
memset(&ar,0x00,sizeof(ar));//清空结构
printf("请输入员工姓名:");
memset(name,0x00,sizeof(name));
scanf("%s",name);
while(fread(&ar,sizeof(ar),1,fp) == 1)//循环复制,与输入姓名相匹配的不复制
{
if(strcmp(name,ar.name) != 0)
{
fwrite(&ar,sizeof(ar),1,fpn);//不相同,则复制
}
memset(&ar,0x00,sizeof(ar));
}
fclose(fp);
fclose(fpn);
remove(ARFILE);//删除原档案文件
rename("./tmpfile",ARFILE);//复制好的新文件重命名为档案文件
printf("删除员工资料成功,按任意键继续...\n");

getchar();//清楚缓冲区残留的\n
getchar();//等待回车
}

/*查询员工函数==================================*/
void queryuser()
{
int found;
char name[10];
struct arstruct ar;
FILE *fp;
if((fp = fopen(ARFILE,"r")) == NULL)
{
return;
}
memset(&ar,0x00,sizeof(ar));
printf("请输入员工姓名:");
memset(name,0x00,sizeof(name));
scanf("%s",name);
found=0;
while(fread(&ar,sizeof(ar),1,fp) == 1)
{
if(strcmp(name,ar.name) == 0)
{
found=1;
break;
}
memset(&ar,0x00,sizeof(ar));
}
fclose(fp);
if(found)
{
printf("姓名=%s\n",ar.name);
printf("年龄=%d\n",ar.age);
printf("手机=%s\n",ar.tele);
}
else
{
printf("没有员工%s的数据\n",name);
}

getchar();//清楚缓冲区残留的\n
getchar();//等待回车
}

/*增加员工函数==================================*/
void insertuser()
{
struct arstruct ar;
FILE *fp;
if((fp = fopen(ARFILE,"a")) == NULL)
{
return;
}
memset(&ar,0x00,sizeof(ar));
printf("请输入员工姓名:");
scanf("%s",ar.name);
printf("请输入员工年龄:");
scanf("%d",&(ar.age));
printf("请输入员工手机号码:");
scanf("%s",ar.tele);
if(fwrite(&ar,sizeof(ar),1,fp) < 0)
{
perror("fwrite");
fclose(fp);
return;
}
fclose(fp);
printf("增加新员工成功,按任意键继续...\n");

getchar();//清楚缓冲区残留的\n
getchar();//等待回车
}

/*主程序,输出结果==================================*/
int main(void)
{
char c;
while(1)
{
printf("\033[2J");//清屏。也可使用system("clear")
printf(" *员工档案管理系统*\n");
printf("---------------------------\n");
printf(" 1.录入新员工档案 \n");
printf(" 2.查看员工档案 \n");
printf(" 3.删除员工档案 \n");
printf(" 0.退出系统 \n");
printf("---------------------------\n");
switch((c=getchar()))
{
case '1':
insertuser();
break;
case '2':
queryuser();
break;
case '3':
removeuser();
break;
case '0':
return 0;
default:
break;
}
}
}