==========================================================================
Filename: test.c
Description: 字节序的检查
Version: 1.0
Created: 2014年08月02日 17时44分45秒
Revision: none
Compiler: gcc
CopyRight: open , free , share
Author: yexingkong(zhangbaoqing)
Email: [email protected]
Company: Xi'an University of post and Telecommunications
==========================================================================
#include
#include
int main(int argc, char *argv[])
{
int i = 0;
union {
int value;
char ch[sizeof(int)];
}test;
test.value = 0x04030201;
for (i = 0;i < sizeof(int); i++)
{
printf("%p --> %#x\n",&test.ch[i],test.ch[i] );
}
printf("\n");
return EXIT_SUCCESS;
}
通过打印对比内存中所存的数据的以及数据单元内存地址就可观察出来。。。。
- 在网络上传输数据时,由于数据传输的两端可能对应不同的硬件平台,采用的存储字节顺序也可能不一致,因此TCP/IP协议规定了在网络上必须采用网络字节顺序(也就是大端模式).而现代PC大多采用小端模式,因此小端模式又被称为主机字节序。为了解决这种网络上两台主机使用不同字节序所造成的错误,所有就有了,发送端总是把要发送的数据转化成大端字节序数据后再发送,而接收端知道对方传过来的数据总是采用大端字节序,所以接收端可以根据自身的字节序决定是否对接收的数据进行转换(小端机转换,大端机不转换)。
1.结构struct sockaddr定义了一种通用的套接字地址,它在linux/socket.h中定义代码如下:
struct sockaddr {
unsigned short sa_family; //地址类型,AF_XXX
char sa_data[14]; //14字节的协议地址
};
其中,成员sa_family表示套接字的协议族类型,对应于TCP/IP协议该值为AF_INTI;成员sa_data,存储具体的协议地址。sa_data之所以被定义成14个字节,因为有的协议族使用较长的地址格式。一般在编程中并不对该结构体进行操作,而是使用另一个与它等价的数据结构:sockaddr_in。
2.每种协议族都有自己的协议地址格式,TCP/IP协议族的地址格式为结构体 struct sockaddr_in,它在netinet/in.h头文件中定义,格式如下:
struct sockaddr_in {
unsigned short sin_family; //地址类型
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //填充字节,一般赋值为0
};
其中,成员sin_family表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET. sin_port是端口号,sin_addr用来存储32为的IP地址,数组sin_zero为填充字段,一般为0.
3.IP数据结构,struct in_addr的定义如下:
struct in_addr {
unsigned long s_addr; //IP地址,要使用网络字节序表示
}
结构体sockaddr的长度为16字节,结构体sockaddr_in的长度也为16字节,通常在编写基于TCP/IP协议的网络程序时,使用结构体sockaddr_in来设置地址,然后通过强制类型转换成sockaddr类型。
以下是设置地址信息的示例代码:
struct sockaddr_in sock;
sock.sin_family = AF_INET; //设置使用IPV4 TCP/IP协议
sock.sin_port = htons(80); //设置端口号
sock.sin_addr.s_addr = inet_addr("192.168.1.132"); //设置地址
memset(sock.sin_zero,0,sizeof(sock.sin_zero)); //将数据sin_zero清0
memset()函数原型为:
memset(void *s, int c, size_t n);
它将s指向的内存区域的前n个字节赋值为C指定的值。
1.网络字节序转换函数
#include
uint16_t htons(uint16_t hosts);
uint32_t htonl(uint32_t hostl);
uint16_t ntohs(uint16_t nets);
uint32_t ntohl(uint32_t netl);
2.IP地址转换函数
人们习惯用可读性好的点分十进制来表示IP地址,但编程中我们需要先把它们转化为整数(二进制)方便使用,而记录日志时则相反,我们要把整数表示的IP地址转化为可读的字符串。
#include
in_addr_t inet_addr(const char *str);
int inet_aton(const char *str, struct in_addr *numstr);
char *inet_ntoa(struct in_addr inaddr);
函数中a代表ASCII串: n 代表数值(numeric)格式,是存在与套接字地址结构中的二进制值。
1.创建套接字
#include
#include
int socket(int domain, int type, int protocal);
2.建立链接(客户端会用到)**
#include
#include
int connect(int sockfd, const struct sockaddr *sery_addr, socklen_t addr_len);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0 , sizeof(struct sockaddr_in));//<==>bzero(&serv_addr,sizeof(struct sockaddr_in)); 将sev_addr的各个字段清0
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(80); //转换字节序
if (inet_aton("192.168.1.132", &serv_addr.sin_addr) < 0)
{
perror("inet_aton");
exit(1);
}
//sock_fd为socket()函数的第一个参数值.
if (connect(sock_fd,(struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0)
{
perror("connect");
exit(1);
}
3.绑定套接字
#include
#include
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
4.在套接字上监听
#include
int listen(int sockfd, int backlog);
如下图:
图的意思就是,当服务器收到客户端发送的建立链接的SYN分节,TCP在未完成队列中创建一个新的条目,然后发送服务器的SYN分节到客户端,并附带对客户SYN的确认ACK。这个条目一直保存在未完成三次握手完毕,该条目将从未完成队列移至已完成队列的队尾。当进程调用accept()函数是,取出已完成链接队列头条目返回给进程,队列为空,进程将睡眠,直到有新的条目到达时才唤醒。
5.接受连接
#include
#include
int accept(int sockfd, struct sockaddr * addr, socklen_t *addrlen);
int client_fd;
int client_len;
struct sockaddr_in client_addr; //保存客户端的Ip和端口等信息
.....
client_len = sizeof(struct sockaddr_in);
client_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0)
{
perror("accept");
exit(1);
}
6.发送数据(TCP)
#include
#include
ssize_t send(int client_fd, const void *msg, size_t len, int flags);
#define BUFFERSIZE 1000 //定义一次发送数据的长度
char send_buff[BUFFERSIZE];
....
if (send(client_fd, send_fd, len, 0) < 0)
{
perror("send");
exit(1);
}
7.接收数据(TCP)
#include
#include
ssize_t recv(int client_fd, void *buf, size_t len, int flags);
8.发送数据(UDP)
#include < sys/types.h >
#include < sys/socket.h >
int sendto ( int s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen ) ;
struct sockaddr_in addr;
int addr_len = sizeof(struct sockaddr_in);
char buffer[256];
//填写sockaddr_in 结构
bzero ( &addr, sizeof(addr) );
addr.sin_family=AF_INET;
addr.sin_port=htons(PORT);
addr.sin_addr.s_addr=htonl(INADDR_ANY) ; // INADDR_ANY表示0.0.0.0,所有地址,不确定地址
if (sendto(sockfd,buffer,len,0,(struct sockaddr *)&addr,addr_len) < 0)
{
perro("sendto");
exit(1);
}
9.接收数据(UDP)
#include
#include
int recvfrom(int s,void *buf,int len,unsigned int flags ,struct sockaddr *from ,int *fromlen);
char recv_buf[256];
struct sockaddr_in src_addr;
int src_len;
src_len = sizeof(struct sockaddr_in);
if (recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0 ,(struct sockaddr *)&src_addr, &ser_len) < 0)
{
perror("recvfrom");
exit(1);
}
10.关闭套接字
#include
int close(int fd);
#include
int shutdown(int sockfd, int howto);
可选值 | 含义 |
---|---|
SHUT_RD | 关闭sockfd上读的一半。应用程序不能再针对socket文件描述符执行读操作,并且该socket接收缓冲区中的数据都被丢弃 |
SHUT_WR | 关闭sockfd上写的一半,sockfd的发送缓冲区中的数据会在真正关闭链接前全部发送出去,应用程序不可再对该socket文件描述符执行写操作。 |
SHUT_RDWR | 同时关闭sockfd上的读写 |
由此可见,shutdown能够分别关闭socket上的读或写,或者都关闭。而close在关闭链接时只能将socket上的读和写同时关闭。
shutdow成功时返回0,失败则返回-1,并设置error.
进程是一个动态的实体,是程序的一次执行过程。进程是操作系统资源分配的基本单位。进程和程序的区别在于,进程是动态的,程序是静态的。进程是运行中的程序,程序是一些保存在磁盘上的可执行的代码。
每个进程都是通过唯一的进程ID来标识的。进程ID是一个非负整数。获得进程ID,可通过如下函数:
函数 | 功能 |
---|---|
pid_t getpid(id) | 获得进程ID |
pid_t getppid(id) | 获得父进程ID |
进程控制包括创建进程,执行新进程,退出进程以及改变进程优先级等。
函数 | 功能 |
---|---|
fork | 用于创建一个新进程 |
exit | 用于终止进程 |
exec | 用于执行一个应用程序 |
wait | 将父进程挂起,等待子进程终止 |
nice | 改变进程的优先级 |
getpid | 获取当前进程的进程ID |
#include
#include
pid_t fork(void);
特别说明:
- 一般情况下,函数最多有一个返回值,但fork函数非常特殊,它有两个返回值. 即调用一次,返回两次.
- 成功调用后,当前进程实际上已经分裂为两个进程,一个是原来的父进程,另一个是刚刚创建的子进程,父子进程在调用fork函数的地方分开.
- fork函数有两个返回值,一个是父进程调用fork函数后的返回值,该返回值是刚刚创建子进程的ID;另一个是子进程中fork函数的返回值,该返回值是0.
- 两次返回不同的值,子进程返回0,父进程返回值为新创建进程ID,这样可以用来区分父,子进程。
- 函数调用失败,返回-1.
- 父子进程的执行顺序是无序的,由内核调度算法来决定。
- 子进程的代码与父进程完全相同,同时它还会复制(区别于:共享)父进程的数据(堆,栈数据和静态数据).数据的复制采用的所谓写时复制(copy on write),即只用在任一进程(父/子)对数据执行了写操作时,复制才会发生(先是缺页中断,然后操作系统给子进程分配内存并复制父进程数据)。
- 此外,创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数加1,父进程的用户根目录,当期工作目录等变量的引用计数均会加1.
在这一过程中,父进程等待客户端请求。当这种请求到达时,父进程调用fork函数,产生一个子进程,由子进程对该请求做处理。父进程则继续等待下一个客户的服务请求。这种情况下,在fork函数之后,父,子进程需要关闭各自不使用的描述符。