-
网络编程
编辑
网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是
数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理,代码、开发工具、数据库、
服务器架设和
网页设计这5部分你都要接触。
静态代码
静态代码是
服务器不解析直接发送给客户端的部分,用做布局效果,一般不用于数据库操作
静态代码分
html,
javascript,
css等,其中
[1]
html语言是基础,要学网络编程就先学html语言.javascript用于实现某些特效,css是样式语言.这3个语言组合起来,可以设计出美妙的
网页效果
动态代码
开发工具有很多种,我推荐一种,网络3剑客,其中dw是开发代码的,fw是做图的.flash是做动画的.
数据库要结合你学的
动态语言来选择,asp系列的,你可以使用access,大型点使用mySQL.
php和mySQL是很好的搭档.
服务器架设也是结合你学的动态语言的,windows下安装
iis很方便,iis可以运行asp,安装
.net框架后能运行,这两者架设相对简单,也是我推荐你入门学
asp的原因.
php一般安装apache服务器,jsp一般安装
tomcat服务器.只有架设好服务器,才能浏览动态语言编写的程序.
虽然是编程,但是总会涉及到
网页设计部分,还是要去学学怎么简单的作图和动画。
网络编程
编辑
网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是
数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理,代码、开发工具、数据库、
服务器架设和
网页设计这5部分你都要接触。
静态代码
静态代码是
服务器不解析直接发送给客户端的部分,用做布局效果,一般不用于数据库操作
静态代码分
html,
javascript,
css等,其中
[1]
html语言是基础,要学网络编程就先学html语言.javascript用于实现某些特效,css是样式语言.这3个语言组合起来,可以设计出美妙的
网页效果
动态代码
开发工具有很多种,我推荐一种,网络3剑客,其中dw是开发代码的,fw是做图的.flash是做动画的.
数据库要结合你学的
动态语言来选择,asp系列的,你可以使用access,大型点使用mySQL.
php和mySQL是很好的搭档.
服务器架设也是结合你学的动态语言的,windows下安装
iis很方便,iis可以运行asp,安装
.net框架后能运行,这两者架设相对简单,也是我推荐你入门学
asp的原因.
php一般安装apache服务器,jsp一般安装
tomcat服务器.只有架设好服务器,才能浏览动态语言编写的程序.
虽然是编程,但是总会涉及到
网页设计部分,还是要去学学怎么简单的作图和动画。
套接字地址
- Linux系统的套接字可以支持多种协议,每种不同的协议都是用不同的地址结构。
- 在头文件中定义了一个通用套接字地址结构sockaddr:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
- 为了处理struct sockaddr,程序员创造了一个并列的大小相同结构:struct sockaddr_in(“in”代表”Internet”。)
- struct sockaddr_in在/usr/include/netinet/in.h中定义:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 填充特定协议地址时使用sockaddr_in
- 作为bind()、connect()、sendto()、recvfrom()等函数的参数时需要使用sockaddr,
- 这时要通过指针强制转换的方式转为struct sockaddr 指针。
IPv4地址结构示例
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
IPV6套接字地址结构sockaddr_in6
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
IP地址转换函数
- inet_aton():将字符串形式的IP地址转换成二进制形式的IP地址,成功返回1,否则返回0,转换后的IP地址存储在参数inp中。
- inet_ntoa():将32位二进制形式的IP地址转换为数字点形式的IP地址,结果在函数返回值中返回。
- 1
- 2
- 1
- 2
网络字节顺序
- 字节序,顾名思义字节的顺序,就是大于一个字节的数据在内存中的存放顺序。
- 在跨平台以及网络程序应用中字节序才是一个应该被考虑的问题。
- 网络字节序是TCP/IP规定的一种数据表示格式,与具体的CPU类型、操作系统无关,从而可以保证数据在不同主机之间传输时能被正确解释。网络字节顺序采用big endian(大端字节序)。
- Intel x86系列CPU使用的都是little endian(小端字节序)
- 大端字节序(big-endian):低地址存放最高有效字节
- 小端字节序(little-endian):低地址存放最低有效字节
- 例如数字0x12345678(DWORD)在两种不同字节序CPU中的存储顺序如下所示:
字节顺序转换函数
- 下面四个函数分别用于长整型和短整型数在网络字节序和主机字节序之间进行转换,其中s指short,l指long,h指host,n指network
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
什么时候要考虑字节序问题
- 如果是应用层的数据,即对TCP/IP来说是透明的数据,不用考虑字节序的问题。因为接收端收到的顺序是和发送端一致的
- 但对于TCP/IP的IP地址、端口号来说就不一样了,例如
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- 因为网络字节序是big endian,即低地址存放的是数值的高位,所以TCP/IP实际上把这个port解释为0x1200(十进制4608)。
- 本来打算是要在端口18建立连接的,但TCP/IP协议栈却在端口4608建立了连接
套接字的工作原理
- INET 套接字就是支持 Internet 地址族的套接字,它位于TCP协议之上,BSD套接字之下,
- 如图所示,这里也体现了Linux网络模块分层的设计思想(图在PPT里,自己想象吧…)
- INET和 BSD 套接字之间的接口通过 Internet 地址族套接字操作集实现,这些操作集实际是一组协议的操作例程,
- 在include/linux/net.h中定义为proto_ops:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 这个操作集类似于文件系统中的file_operations结构。BSD套接字层通过调用proto_ops 结构中的相应函数执行任务。
- BSD套接字层向 INET 套接字层传递socket数据结构来代表一个BSD套接字,
socket 定义
- socket结构在include/linux/net.h中定义:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 但在INET套接字层中,它利用自己的sock数据结构来代表该套接字,因此,这两个结构之间存在着链接关系
- Sock结构定义于include/net/sock.h(此结构有80多行,在此不予列出
- 在BSD的socket数据结构中存在一个指向sock的指针sk,而在sock中又有一个指向socket的指针,
- 这两个指针将BSD socket数据结构和sock数据结构链接了起来。
- 通过这种链接关系,套接字调用就可以方便地检索到sock数据结构
- 实际上,sock数据结构可适用于不同的地址族,它也定义有自己的协议操作集proto
- 进程在利用套接字进行通讯时,采用客户-服务器模型。服务器首先创建一个套接字,并将某个名称绑定到该套接字上,套接字的名称依赖于套接字的底层地址族,但通常是服务器的本地地址
/etc/services 文件
- 对于INET套接字来说,服务器的地址由两部分组成:服务器的IP地址和服务器的端口地址。已注册的标准端口可查看/etc/services 文件
- 将地址绑定到套接字之后,服务器就可以监听请求连接该绑定地址的传入连接
- 连接请求由客户生成,它首先建立一个套接字,并指定服务器的目标地址以请求建立连接
- 传入的连接请求通过不同的协议层到达服务器的监听套接字
- 服务器接收到传入请求后,如果能够接受该请求,服务器必须创建一个新的套接字来接受该请求并建立通信连接(用于监听的套接字不能用来建立通信连接),这时,服务器和客户就可以利用建立好的通信连接传输数据
- BSD套接字上的详细操作与具体的底层地址族有关,底层地址族的不同实际意味着寻址方式、采用的协议等的不同
- Linux 利用BSD套接字层抽象了不同的套接字接口。在内核的初始化阶段,内建于内核的不同地址族分别以BSD套接字接口在内核中注册
- 然后,随着应用程序创建并使用BSD套接字
- 内核负责在BSD套接字和底层的地址族之间建立联系。这种联系通过交叉链接数据结构以及地址族专有的支持例程表建立
- 在内核中,地址族和协议信息保存在inet_protos向量中,其定义于include/net/protocol.h
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
建立套接字
- Linux在利用socket()系统调用建立新的套接字时,需要传递套接字的地址族标识符、套接字类型以及协议,其函数定义于net/socket.c中
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
sockfs
- 实际上,套接字对于用户程序而言就是特殊的已打开的文件。内核中为套接字定义了一种特殊的文件类型,形成一种特殊的文件系统sockfs
- 所谓创建一个套接字,就是在sockfs文件系统中创建一个特殊文件,或者说一个节点,并建立起为实现套接字功能所需的一整套数据结构
- 所以,函数sock_create()首先是建立一个socket数据结构,然后将其“映射”到一个已打开的文件中,进行socket结构和sock结构的分配和初始化
- 实际上,socket结构与sock结构是同一事物的两个方面。如果说socket结构是面向进程和系统调用界面的,那么sock结构就是面向底层驱动程序的
- 把与文件系统关系比较密切的那一部分放在socket结构中,把与通信关系比较密切的那一部分则单独组成一个数据结构,即sock结构
- 由于这两部分数据在逻辑上本来就是一体的,所以要通过指针互相指向对方,形成一对一的关系
在INET BSD套接字上绑定(bind)地址
- 为了监听传入的Internet 连接请求,每个服务器都需要建立一个INET BSD套接字,并且将自己的地址绑定到该套接字
- 将地址绑定到某个套接字上之后,该套接字就不能用来进行任何其他的通信,因此,该socket数据结构的状态必须为TCP_CLOSE
- 传递到绑定操作的sockaddr数据结构中包含要绑定的 IP地址以及一个可选的端口地址。被绑定的IP地址保存在sock数据结构的rcv_saddr和 saddr域中,这两个域分别用于哈希查找和发送用的IP地址。
- 端口地址是可选的,如果没有指定,底层的支持网络会选择一个空闲的端口
- 当底层网络设备接受到数据包时,它必须将数据包传递到正确的 INET 和 BSD 套接字以便进行处理,因此,TCP维护多个哈希表,用来查找传入 IP 消息的地址,并将它们定向到正确的socket/sock 对
- TCP 并不在绑定过程中将绑定的sock数据结构添加到哈希表中,在这一过程中,它仅仅判断所请求的端口号当前是否正在使用。在监听操作中,该 sock 结构才被添加到 TCP 的哈希表中
在INET BSD套接字上建立连接 (connect)
- 创建一个套接字之后,该套接字不仅可以用于监听入站的连接请求,也可以用于建立出站的连接请求。不论怎样都涉及到一个重要的过程:建立两个应用程序之间的虚拟电路。出站连接只能建立在处于正确状态的 INET BSD 套接字上,
- 因此,不能建立于已建立连接的套接字,也不能建立于用于监听入站连接的套接字。也就是说,该 BSD socket 数据结构的状态必须为 SS_UNCONNECTED
- 在建立连接过程中,双方 TCP 要进行三次“握手”。如果 TCP sock 正在等待传入消息,则该 sock 结构添加到 tcp_listening_hash 表中,这样,传入的 TCP 消息就可以定向到该 sock 数据结构
监听(listen) INET BSD 套接字
- 当某个套接字被绑定了地址之后,该套接字就可以用来监听专属于该绑定地址的传入连接。网络应用程序也可以在未绑定地址之前监听套接字,这时,INET 套接字层将利用空闲的端口编号并自动绑定到该套接字。套接字的监听函数将 socket 的状态改变为TCP_LISTEN
- 当接收到某个传入的 TCP 连接请求时,TCP 建立一个新的 sock 数据结构来描述该连接。当该连接最终被接受时,新的 sock 数据结构将变成该 TCP 连接的内核bottom_half部分,这时,它要克隆包含连接请求的传入 sk_buff 中的信息,并在监听 sock 数据结构的 receive_queue 队列中将克隆的信息排队。克隆的 sk_buff 中包含有指向新 sock 数据结构的指针
接受连接请求(accept)
- 接受操作在监听套接字上进行,从监听 socket 中克隆一个新的 socket 数据结构。其过程如下:
- 接受操作首先传递到支持协议层,即INET中,以便接受任何传入的连接请求。接受操作可以是阻塞的或是非阻塞的。非阻塞时,若没有可接受的传入连接,则接受操作将失败,而新建立的socket数据结构被抛弃。阻塞时,执行阻塞操作的网络应用程序将添加到等待队列中并保持挂起直到接收到一个TCP连接请求为止。
- 当连接请求到达之后,包含连接请求的sk_buff被丢弃,而由TCP建立的新sock数据结构返回到INET套接字层,在这里,sock数据结构和先前建立的新socket数据结构建立链接。而新socket的文件描述符(fd)被返回到网络应用程序,此后,应用程序就可以利用该文件描述符在新建立的INET BSD套接字上进行套接字操作
套接字为用户提供的系统调用
- 见PPT 3.7
getsockname()函数
- getsockname(): 获取与当前套接字绑定的IP地址及端口
- 1
- 2
- 1
- 2
- 返回值:成功返回0,失败返回-1,并在errno中设置错误代码。
- 错误代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
getpeername()函数
- 1
- 2
- 1
- 2
- 返回值:成功返回0,失败返回-1,并在errno中设置错误代码。
- 错误代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
gethostbyname()和gethostbyaddr()
- gethostbyname():主机名转换为IP地址
- gethostbyaddr():IP地址转换成主机名
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
getservbyname()和getservbyport()
- getservbyname():根据给定名字查找相应服务,返回服务的端口号
- getservbyport():给定端口号和可选协议查找相应服务
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
字节处理函数 bzero bcopy bcmp memset memcpy memcmp
- 套接字地址是多字节数据,不是以空字符结尾的,Linux提供两组函数来处理多字节数据。一组函数以b开头,适合BSD系统兼容的函数;另一组函数以mem开头,是ANSI C提供的函数
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 函数bzero将参数s指定的内容的前n个字节设置为0,通常用它来将套接字地址清零
- 函数bcopy从参数src指定的内存区域拷贝指定数目的字节内容到参数dest指定的内存区域
- 函数bcmp比较参数s1指定的内存区域和参数s2指定的内存区域的前n个字节内容,相同则返回0,否则返回非0
- 将参数s指定的内存区域的前n个字节设置为参数c的内容
- 类似于bcopy,但bcopy能处理参数src和参数dest所指定的区域有重叠的情况,而memcpy不能
- 比较参数s1和参数s2指定区域的前n个字节内容,相同则返回0,否则返回非0
小结
- 套接字标识TCP/IP的连接
- 使用套接字要注意:
- 1、sockaddr与sockaddr_in的区别
- 2、网络字节顺序
- 了解套接字的工作原理
- 掌握套接字的通信过程
给出在linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到客户端的连接后,发送数据给客户端;客户端在接受到数据后打印出来,然后关闭。程序里有详细的说明,其中对具体的结构体和函数的实现可以参考其他资料。
程序说明: 这里服务器的端口号和ip地址使用固定的设置,移植时可以根据具体情况更改,可以改写为参数传递更好,这里为了方便,使用固定的。
移植时服务端可以不用更改,编译后可直接运行;客户端将ip改为服务器的地址,然后编译运行。可以使用netstat 进行查看相应的运行状态。
运行截图:
c语言网络编程-标准步骤,真的很简单啊
server.c
复制代码 代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 4444
#define BACKLOG 5
int main(int argc, char *argv[]) {
int sock_fd, new_fd;
struct sockaddr_in server_addr, client_addr;
int sin_size;
int nbytes;
int on = 1;
char buffer[1024];
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1) {
perror("bind");
exit(1);
}
if (listen(sock_fd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
printf("Server start... \n");
while (1) {
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sock_fd, (struct sockaddr *)(&client_addr), &sin_size)) == -1) {
perror("accept");
exit(1);
}
printf("Server get connection from %s\n", inet_ntoa(client_addr.sin_addr));
if ((nbytes = read(new_fd, buffer, 1024)) == -1) {
perror("read");
exit(1);
}
buffer[nbytes] = '\0';
printf("Server received: %s\n", buffer);
close(new_fd);
}
return 0;
}
client.c
复制代码 代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 4444
int main(int argc, char *argv[]) {
int sock_fd;
struct sockaddr_in server_addr;
struct hostent *host;
char buffer[1024];
if (argc < 2) {
perror("Need hostname");
exit(1);
}
if ((host = gethostbyname(argv[1])) == NULL) {
perror("gethostbyname");
exit(1);
}
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
if (connect(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1) {
perror("connect");
exit(1);
}
printf("Please input something:\n");
fgets(buffer, 1024, stdin);
write(sock_fd, buffer, strlen(buffer));
close(sock_fd);
return 0;
}