第6课.网络编程
Posted huangdengtao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第6课.网络编程相关的知识,希望对你有一定的参考价值。
1.TCP和UDP的区别
(1)TCP是面向连接的协议,UDP是面向无连接的协议。
(2)TCP对系统资源要求较多,UDP对系统资源要求较少。
(3)TCP是数据流模式,UDP是数据报模式。
(4)TCP保证数据顺序及数据的正确性,UDP可能会丢包。
2.简述TCP/UDP服务器端创建流程与客户端创建流程
TCP服务器端创建流程:
创建通信用文件描述符(socket)-->设置端口号和IP地址(为绑定做准备)-->绑定(bind)-->监听(listen)-->接受请求,建立连接(accept)-->发送与接收消息(send/recv)-->关闭文件(close)
TCP客户端创建流程:
创建通信用文件描述符(socket)-->设置端口号和IP地址-->发起连接请求(connect)-->接受与发送消息(send/recv)-->关闭文件(close)
UDP服务器端创建流程:
创建通信用文件描述符(socket)-->设置端口号和IP地址(为绑定做准备)-->绑定(bind)-->接受和发送消息(sendto && recvfrom)-->关闭文件(close)
UDP客户端创建流程:
创建通信用文件描述符(socket)-->设置端口号和IP地址-->接受与发送消息(sendto && recvfrom)-->关闭文件
3.函数解析
socket函数
获得一个文件描述符(文件句柄)。为了执行网络输入输出,一个进程必须做的一件事就是调用socket函数获得一个文件描述符(文件句柄)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
成功:return 费负描述符 失败:-1
domain: 指明协议簇,目前支持5中协议簇,最常用的有AF_INET (IPv4协议)和AF_INET6 (IPv6协议)
type : 指明套接口类型,有3种类型可选:
SOCK_STREAM:字节流套接口
SOCK_DGRAM :数据报套接口
SOCK_RAW :原始套接口;如果套接口不是原始套接口,那么第三个参数就为0
bind函数
把文件句柄和IP,端口捆绑在一起。为套接口分配一个本地IP和协议端口,对于网络协议,协议地址是32位IPv4或128位IPv6地址与16位的TCP/UDP端口的组合。如指定一个通用的通配端口为0,调用bind时内核将选择一个临时端口。如果指定一个通用通配IP地址,则要等建立连接后内核才能选择一个本地IP地址。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
成功:0 失败:-1
sockfd :文件句柄
addr :指向特定协议的地址结构的指针
这里不使用sockaddr这个结构体,而是使用下面这个结构体
struct sockaddr_in {
short int sin_family; /* 地址族,AF_xxx 在socket编程中只能是AF_INET */
unsigned short int sin_port; /* 端口号 (使用网络字节顺序) */
struct in_addr sin_addr; /* 存储IP地址 4字节 */
unsigned char sin_zero[8]; /* 总共8个字节,实际上没有什么用,只是为了和struct sockaddr保持一样的长度,设为0就好 */
};
addrlen:第二个参数结构体的长度
listen函数
监听。listen函数仅被TCP服务器调用,它的作用是将用socket创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
成功:0 失败:-1
sockfd :文件句柄
backlog:最多可以一次监听多少路。这个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因。内核要维护两个队列。以完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三路握手未完成的连接。accept函数是从以连接队列中取连接返回给进程,当已连接队列为空时,进程将进入睡眠状态。
accept函数
等待连接。accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空则进程进入睡眠状态
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
成功:非负描述符 失败:-1
sockfd :文件句柄
addr :接收到的数据存储在这里。
addrlen:同bind函数
connect函数
建立联系。当用connect建立了套接口后可以用connect为这个套接口指明远程端地址。如果字节流套接口(TCP),connect就使用三次握手建立一个链接。如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据区握手。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
成功:0 失败:-1
sockfd :客户端文件句柄
addr :见bind函数
addrlen:同上
send函数
用来发送数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
成功:返回写出的字数 失败:-1
sockfd:文件句柄。对于服务器是accept函数返回的已连接的文件句柄。对于客户端是调研员socket函数返回的文件句柄
buf :指向一个用于发送信息的数据缓冲区
len :指明传送数据的大小
flags :传输标志
sendto函数
用于无连接(没有connect函数)的数据报,socket方式下进行传输。由于本地socket并没有与远端服务器建立联系,所以在发送数据时应指明目的地址
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
成功:返回写出的字数 失败:-1
dest_addr:用法和connect中一样
addrlen :同上
recv函数
用来接收数据
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
成功:返回读入的字节数 失败:-1
参数同上
recvfrom函数
简介同sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数同sendto
close函数
结束传输。当所有数据操作结束后,你可以调用close函数来释放socket,从而停止改socket上的任何操作
#include <unistd.h>
int close(int fd);
你也可以调用shutdown()函数来关闭socket。该函数允许你至停止在某个方向上的数据传输,而另一个方向上的数据传输继续进行。如你可以关闭socket的写操作而允许改socket继续接受数据直到读入所以数据。
int shutdown(int sockfb, int how);
成功:0 失败:-1
sockfb:需要关闭的socket的文件句柄。
how :允许shutdown操作选择
0:不允许继续接收操作
1:不允许继续发送操作
3:不允许继续发送和接收操作
4.代码解析
TCP
server.c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
/* socket
* bind
* listen
* accept
* send/recv
*/
#define SERVER_PORT 8888
#define BACKLOG 10
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
signal(SIGCHLD,SIG_IGN); //防止子进程僵死
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!
");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!
");
return -1;
}
iRet = listen(iSocketServer, BACKLOG);
if (-1 == iRet)
{
printf("listen error!
");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)
{
iClientNum++;
printf("Get connect from client %d : %s
", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork())
{
/* 子进程的源码 */
while (1)
{
/* 接收客户端发来的数据并显示出来 */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '