这一系列博客将用于记录学习《TCP/IP网络编程》的笔记。
先上代码。下面是服务器端的代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//提供针对系统调用的封装
#include <unistd.h>
//提供用于网络字节序转换的函数
#include <arpa/inet.h>
#include <sys/socket.h>
//错误信息展示
void error_handling (char *message);
int main(int argc, char *argv[]){
int serv_sock, clnt_sock;
struct sockaddr_in serv_addr, clnt_addr;
socklen_t clnt_addr_size;
char message[] = "Hello Word!";
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("Acceot Error");
write(clnt_sock, message, sizeof(message));
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/socket.h>
//错误信息展示
void error_handling (char *message){
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char* argv[]){
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if (argc != 3){
printf ("Uasge : %s <IP> <PORT>", 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");
str_len = read(sock, message, sizeof(message)) - 1;
if (str_len == -1)
error_handling("Read Error");
printf("Message Is %s\n", message);
close(sock);
return 0;
}
创建socket
对于Linux
系统而言,一切皆文件。所以我们一开始的时候就要创建一个文件,也就是我们的socket
。有了文件,才能对文件进行各种各样千奇百怪的操作才是嘛,所以我们创建一个socket
文件 先
int socket(int domain, int type, int protocol)
socket
函数会返回一个描述符,也就是一个int
整形数字,在Linux
下,这个数字就是与系统沟通好的,用来描述当前socket
的一个暗号。
描述socket
有了这个socket
文件之后呢,我们需要告诉操作系统,这个文件的一些特征。socket
函数大概就类似于告诉系统,我要创建一个mp3
文件还是一个txt
文件。而接下来的这段代码,就是要告诉系统,这个有多大,它可以接受怎样的内容
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]));
我们先将文件给清空,然后再一一告诉系统这个文件相关的东西。
-
AF_INET
IPv4 网络协议的套接字类型 -
htonl(INADDR_ANY)
这里告诉系统,这个socket
监听所有的网卡,就是指定地址为0.0.0.0
的地址 -
htons(atoi(argv[1]))
这里告诉系统,监听哪一个端口,该端口由我们指定
其中htons
和htons
代表了将本地的字符串字节序转化为网络字节序,防止因为本地的大小端存放问题导致出错。
进行绑定
普通的文件创建完了之后,就可以丢那里不管,需要的时候直接写入数据就可以,但毕竟socket
不是一般的文件,它需要从网络上来获取数据,所以我们还需要一步绑定的操作,来告诉操作系统,我这个socket
需要监听来自xxxIP,yyy端口的东西啦。这样当有数据从指定的地址端口来的时候,系统才知道,要送到这个socket
这里。
我们使用bind
函数进行绑定
int bind (int sockfd, struct sockaddr* myaddr, socklen_t addrlen)
通过这个函数,我们就可以绑定到操作系统上,从而接收数据。
开始监听
绑定完了之后,我们需要让socket
去监听丫。bind
只是绑定了端口,但是socket
并没有对这个端口做什么操作,来了数据也不知道,是吧。
使用listen
来监听我们指定的端口
int listen(int sockfd, int backlog)
通过这个操作,当有东西来了的时候,socket
就能够知道,并且能够进行相应的处理。
受理连接
对于一个socket
而言,它不一定是给指定的连接使用的,它大部分情况下都是能够接收很多很多的连接,那我们怎么去区分这不同的连接,跟不同的连接做交互呢?所以需要一个函数来负责获取这些连接,将每一个连接打上不同的记号,方便我们进行处理。因此,就有了accept
函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
很明显,系统也将不同的连接当成了文件,返回了一个个的文件描述符。因此我们能够根据这不同的文件描述符去区分不同的连接。
注意,当socket
没有接收到连接的时候,它会阻塞到这里,直到有连接进来或者出现什么失败为止。
连接服务器
对于客户端的代码而言,基本上都和服务器端的代码相同,但是有个不一样的函数出现在这。对于客户端而言,我需要主动的去找服务器,所以客户端的代码需要一个连接的操作
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen)
通过connect
函数,我们能够连接到想要的服务器,跟其进行通信。
关闭socket
对于任一打开的操作,基本上都要记得关闭,不然就可能出大问题!!
int close(int fd)
这个函数非常简单,传入我们的socket
描述符,系统便能够将这个socket
相关的东西断开,方便下次使用。
写入数据
既然有了网络连接,自然是需要传递数据的。我们把socket
当成了文件看待,所以像其中写入数据自然是使用write
函数
ssize_t write(int fd, const void *buf, size_t nbytes)
这个函数的返回值有点奇怪,它是一种元数据类型(primitive),这里与C语言的语言特点有关,C并未规定int, long short
这些数据类型的大小,而且操作系统也在不断的发生变化,由最初的16位发展到现在的64位,所以如果我们直接使用int
类型的话,可能在之后的代码中需要进行不断的修改,并且可能在某一系统上,int
的大小类型并不符合我们的要求,所以我们利用typedef
进行额外的定义,定义出一系列大小规整的类型。
读取数据
读取数据与写入数据很类似,都是很标准的Linux
文件操作
ssize_t read(int fd, void *buf, size_t nbytes)
通过这个函数,我们能够读取到我们需要的大小的数据
总结
简而言之,创建一个基本的socket
还是比较简单的,过程可能有些繁琐。可以归纳为以下几步。
- 第一步:调用
socket
创建一个socket
- 第二步:调用
bind
函数绑定IP地址和端口号,绑定到操作系统 - 第三步:调用
listen
开始转为可接收请求状态 - 第四步:调用
accept
函数受理连接请求- 客户端需要一个
connect
函数来连接到服务器
- 客户端需要一个
- 利用
write
和read
进行数据的读写操作 - 关闭
socket