看了很多的资料,决定自己写个程序练习练习,于是就自己写了个小程序来练练手:
Server端(linux环境下):
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#define PORT 3456
#define BACKLOG 10
#define MAX_EPOLL_FD 65535
int CreateTcpListenSocket();
int InitEpollFd();
void UseConnectFd(int sockfd);
void setnonblocking(int sock);
static int listenfd;
int main()
{
int epoll_fd;
int nfds;
int i;
struct epoll_event events[50];
struct epoll_event tempEvent;
int sockConnect;
struct sockaddr_in remoteAddr;
int addrLen;
addrLen = sizeof(struct sockaddr_in);
epoll_fd = InitEpollFd();
if (epoll_fd == -1)
{
perror("init epoll fd error.");
exit(1);
}
printf("begin in loop.\n");
while (1)
{
nfds = epoll_wait(epoll_fd, events, 50, 1000);
//sleep(3);
printf("connect num: %d\n", nfds);
if (nfds == -1)
{
perror("epoll_wait error.");
}
for (i = 0; i < nfds; i++)
{
if (listenfd == events[i].data.fd)
{
printf("connected success\n");
sockConnect = accept(events[i].data.fd, (struct sockaddr*)&remoteAddr, &addrLen);
if (sockConnect == -1)
{
perror("accept error.");
continue;
}
setnonblocking(sockConnect);
tempEvent.events = EPOLLIN | EPOLLET;
tempEvent.data.fd = sockConnect;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockConnect, &tempEvent) < 0)
{
perror("epoll ctl error.");
return -1;
}
}
else
{
UseConnectFd(events[i].data.fd);
}
}
}
}
int CreateTcpListenSocket()
{
int sockfd;
struct sockaddr_in localAddr;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("create socket fail");
return -1;
}
setnonblocking(sockfd);
bzero(&localAddr, sizeof(localAddr));
localAddr.sin_family = AF_INET;
localAddr.sin_port = htons(PORT);
localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockfd, (struct sockaddr*)&localAddr, sizeof(struct sockaddr)) == -1)
{
perror("bind error");
return -1;
}
if (listen(sockfd, BACKLOG) == -1)
{
perror("listen error");
return -1;
}
return sockfd;
}
int InitEpollFd()
{
//epoll descriptor
int s_epfd;
struct epoll_event ev;
listenfd = CreateTcpListenSocket();
if (listenfd == -1)
{
perror("create tcp listen socket error");
return -1;
}
s_epfd = epoll_create(MAX_EPOLL_FD);
ev.events = EPOLLIN;
ev.data.fd = listenfd;
if (epoll_ctl(s_epfd, EPOLL_CTL_ADD, listenfd, &ev) < 0)
{
perror("epoll ctl error");
return -1;
}
return s_epfd;
}
void UseConnectFd(int sockfd)
{
char recvBuff[1500];
int recvNum = 0;
recvNum = recv(sockfd, recvBuff, 1500, 0);
if (recvNum == -1)
{
perror("recv error.");
return;
}
recvBuff[recvNum] = '\0';
printf("message: %s \n", recvBuff);
}
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
client端(VC6.0):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
//#include <sockets.h>
//#include <unistd.h>
//#include <sys/types.h>
//#include <sys/socket.h>
//#include <arpa/inet.h>
#pragma comment(lib,"wsock32.lib")
#define PORT 3456
int main()
{
int sockfd;
struct sockaddr_in remoteAddr;
char mesg[] = "This is a client for epoll test";
WSADATA wsdata;
memset(&remoteAddr, 0, sizeof(remoteAddr));
remoteAddr.sin_family = AF_INET;
remoteAddr.sin_port = htons(PORT);
remoteAddr.sin_addr.s_addr = inet_addr("10.40.8.15");
WSAStartup(0x0202,&wsdata);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("create socket fail.");
return -1;
}
WSAAsyncSelect(sockfd, NULL, NULL, 0);
printf("server ip is: %s\n", (char *)inet_ntoa(remoteAddr.sin_addr));
// while (1)
// {
if (connect(sockfd, (struct sockaddr*)&remoteAddr, sizeof(remoteAddr)) == -1)
{
//perror("connect error.");
printf("Error code: %d\n", WSAGetLastError());
return -1;
}
//printf("times\n");
// }
printf("connected!\n");
while (1)
{
send(sockfd, mesg, strlen(mesg), 0);
printf("send over\n");
Sleep(1000);
}
}
posted @
2008-02-19 18:17 吴剑 阅读(990) |
评论 (2) |
编辑 收藏
(申明:此文章属于原创,若转载请表明作者和原处链接 )
/* author: wu.jian (吴剑) English name: Sword
/* date: 2007-12-13
/* purpose: 知识共享
这几天工作上碰到了UTF-8转GB2312的问题,而且是在嵌入式的环境下,没有API可用,查了很多网上的资料,大多调用VC或者linux下自带的接口。在这里我将这两天的工作做个总结。
总的来说分为两大步(这里就不介绍基础知识了):
一、UTF8 -> Unicode
由于UTF8和Unicode存在着联系,所以不需要任何库就可以直接进行转换。首先要看懂UTF8的编码格式:
U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
前面几个1就代表后面几个字节是属于一起的。如果要解析一长串UTF8格式的字符串,这点就很有用了。下面这个函数就是判断前面几个1的(这里有define APP_PRINT printf,这样当release的时候将这个宏定义为空就行了,不需要一个一个去改,又方便重新调试):
int GetUtf8ByteNumForWord(u8 firstCh)
{
u8 temp = 0x80;
int num = 0;
while (temp & firstCh)
{
num++;
temp = (temp >> 1);
}
APP_PRINT("the num is: %d", num);
return num;
}
利用这个函数可以得到字符串中那几个字节是一起的。因为UTF8最大只有6个字节,所以就根据返回值来处理这里我只处理了3个字节和1个字节的UTF8的编码,因为一般来说中文在UTF8中是3个字节。
//将len个字节的UTF8格式的转换成GB2312格式存放在temp预先申请好的缓冲区中
void Utf8ToGb2312(const char* utf8, int len, char *temp)
{
APP_PRINT("utf8->unicode: \n");
APP_PRINT("utf8: [");
for (int k = 0; k < len; k++)
{
APP_PRINT("%02x ", utf8[k]);
}
APP_PRINT("]\n");
int byteCount = 0;
int i = 0;
int j = 0;
u16 unicodeKey = 0;
u16 gbKey = 0;
//循环解析
while (i < len)
{
switch(GetUtf8ByteNumForWord((u8)utf8[i]))
{
case 0:
temp[j] = utf8[i];
byteCount = 1;
break;
case 2:
temp[j] = utf8[i];
temp[j + 1] = utf8[i + 1];
byteCount = 2;
break;
case 3:
//这里就开始进行UTF8->Unicode
temp[j + 1] = ((utf8[i] & 0x0F) << 4) | ((utf8[i + 1] >> 2) & 0x0F);
temp[j] = ((utf8[i + 1] & 0x03) << 6) + (utf8[i + 2] & 0x3F);
//取得Unicode的值
memcpy(&unicodeKey, (temp + j), 2);
APP_PRINT("unicode key is: 0x%04X\n", unicodeKey);
//根据这个值查表取得对应的GB2312的值
gbKey = SearchCodeTable(unicodeKey);
APP_PRINT("gb2312 key is: 0x%04X\n", gbKey);
if (gbKey != 0)
{
//here change the byte
//不为0表示搜索到,将高低两个字节调换调成我要的形式
gbKey = (gbKey >> 8) | (gbKey << 8);
APP_PRINT("after changing, gb2312 key is: 0x%04X\n", gbKey);
memcpy((temp + j), &gbKey, 2);
}
byteCount = 3;
break;
case 4:
byteCount = 4;
break;
case 5:
byteCount = 5;
break;
case 6:
byteCount = 6;
break;
default:
APP_PRINT("the len is more than 6\n");
break;
}
i += byteCount;
if (byteCount == 1)
{
j++;
}
else
{
j += 2;
}
}
APP_PRINT("utf8: [");
for (k = 0; k < j; k++)
{
APP_PRINT("%02x ", temp[k]);
}
APP_PRINT("]\n");
}
二、下面主要谈谈利用查表法来进行Unicode->GB2312的转换,首先下载码表,一般码表都是将GB2312的放在前面,Unicode放在后面,这样对于我们来说不方便使用,所以我转换了下,将Unicode放在前面,而且按照从小到大排好序。(这里只需要考虑都为两个字节的情况,因为前面的UTF8->Unicode并没有将单字节的ASCII转换成Unicode)
(1)做表:(可以到这里下载:http://blog.91bs.com/?action=show&id=20,这里谢谢渣渣的猪窝)
这个是原来的样子:
0x8140 0x4E02 #CJK UNIFIED IDEOGRAPH
0x8141 0x4E04 #CJK UNIFIED IDEOGRAPH
0x8142 0x4E05 #CJK UNIFIED IDEOGRAPH
先弄成(这个可以写个小程序来做,我就是在VC上做的,如果需要可以联系我):
{ 0x4E02 ,0x8140 }, //CJK UNIFIED IDEOGRAPH
{ 0x4E04 ,0x8141 }, //CJK UNIFIED IDEOGRAPH
{ 0x4E05 ,0x8142 }, //CJK UNIFIED IDEOGRAPH
这样就可以把这些放在.h文件中了,下面是我的定义:
typedef struct unicode_gb
{
unsigned short unicode;
unsigned short gb;
} UNICODE_GB;
UNICODE_GB code_table[] =
{
{ 0x4E02, 0x8140 }, //CJK UNIFIED IDEOGRAPH
{ 0x4E04, 0x8141 }, //CJK UNIFIED IDEOGRAPH
{ 0x4E05, 0x8142 }, //CJK UNIFIED IDEOGRAPH
。。。。。。省略
下面这一步也很简单,在VC中用冒泡排序法,对这个数组按照unicode值进行排序,如果需要可以联系我,把最终结果打印出来,在cmd下运行name > 1.txt就输出到文件,这样就有了一个按照unicode排好序的unicode->gb2312码表。
int main(int argc, char *argv[])
{
int num = 0;
UNICODE_GB temp;
int i = 0;
int j = 0;
num = sizeof(code_table) / sizeof(UNICODE_GB);
printf("struct size: %d | total size: %d | num is: %d \n",
sizeof(UNICODE_GB), sizeof(code_table), num);
for (i = 0; i < num; i++)
{
for (j = 1; j < num - i; j++)
{
if (code_table[j - 1].unicode > code_table[j].unicode)
{
temp.unicode = code_table[j - 1].unicode;
temp.gb = code_table[j - 1].gb;
code_table[j - 1].unicode = code_table[j].unicode;
code_table[j - 1].gb = code_table[j].gb;
code_table[j].unicode = temp.unicode;
code_table[j].gb = temp.gb;
}
}
}
printf("here is the code table sorted by unicode\n\n");
for (i = 0; i < num; i++)
{
printf("{\t0x%04X,\t0x%04X\t},\t\n", code_table[i].unicode, code_table[i].gb);
}
printf("\n\n print over!\n");
//以下注释掉的其实就是我用来对原来的码表添加,{,}等用的
/*
char buff[100];
char buff_1[100];
FILE* fp = NULL;
FILE *fp_1 = NULL;
memset(buff, 0, 100);
memset(buff_1, 0, 100);
fp = fopen("table.txt", "rw");
fp_1 = fopen("table_1.txt", "a+");
if ((fp == NULL) || (fp_1 == NULL))
{
printf("open file error!\n");
return 1;
}
while (fgets(buff, 100, fp) != NULL)
{
buff[8] = ',';
fputs(buff, fp_1);
}
*/
return 0;
}
最后就是搜索算法了,前面已经排好序了,现在我们把排好序的码表放在我们真正需要的.h文件中。大家应该猜我用什么算法搜索了吧,二分法。
#define CODE_TABLE_SIZE 21791
//这个表是死的,所以就直接用宏表示长度,不用每次都用size,不过这样可能对移植性不好。
u16 SearchCodeTable(u16 unicodeKey)
{
int first = 0;
int end = CODE_TABLE_SIZE - 1;
int mid = 0;
while (first <= end)
{
mid = (first + end) / 2;
if (code_table[mid].unicode == unicodeKey)
{
return code_table[mid].gb;
}
else if (code_table[mid].unicode > unicodeKey)
{
end = mid - 1;
}
else
{
first = mid + 1;
}
}
return 0;
}
到此,已经能够将UTF8串转换成GB2312了。是一长串哦,而不是单个汉字的编码转换。
posted @
2007-12-13 19:21 吴剑 阅读(26141) |
评论 (63) |
编辑 收藏
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include "ATA_Update.h"
#define MY_SOCK_PORT 2468
int socketId;
BOOL isSetServerAddr;
struct sockaddr sa;
int main(int argc, char *argv[])
{
isSetServerAddr = FALSE;
socketId = socket(PF_PACKET, SOCK_PACKET, htons(0x0003));
if (socketId < 0)
{
perror ("can not get SOCK_PACKET socket");
return 0;
}
//struct sockaddr sa;
memset(&sa, 0, sizeof(struct sockaddr));
sa.sa_family = PF_PACKET;
strcpy(sa.sa_data, "eth0");
if (bind(socketId, &sa, sizeof(sa)) < 0)
{
perror("bind ");
return 0;
}
// int value = 1;
// setsockopt(socketId, SOL_SOCKET, SO_BROADCAST, &value, sizeof(value));
printf("send data test...\n");
while (1)
{
SendData(CMD_BROADCAST, NO_NEED_ACK);
sleep(5);
}
/*
printf("send broad cast pkg...\n");
BlockWaitForUpdate();
printf("get the server addr, after BlockWaitForUpdate\n");
printf("begin to recv data......\n");
RecvData(socketId);
printf("recv data over.........\n");
*/
return 0;
}
BOOL SendData(UINT8 cmdType, UINT8 ackType)
{
int i = 0;
int writeNum = 0;
printf("the cmd is: %d || ack type is : %d \n", cmdType, ackType);
UINT8 sendBuffer[100];
memset(sendBuffer, 0 , 100);
for (i = 0; i < 100; i++)
{
sendBuffer[i] = 16;
}
//00-16-76-A8-6E-AA
sendBuffer[0] = 0x00;
sendBuffer[1] = 0x16;
sendBuffer[2] = 0x76;
sendBuffer[3] = 0xA8;
sendBuffer[4] = 0x6E;
sendBuffer[5] = 0xAA;
//50:78:4C:70:92:EF
sendBuffer[6] = 0x50;
sendBuffer[7] = 0x78;
sendBuffer[8] = 0x4c;
sendBuffer[9]= 0x70;
sendBuffer[10] = 0x92;
sendBuffer[11] = 0xEF;
/*
for (i = 6; i < 12; i++)
{
sendBuffer[i] = 0x00;
}
*/
sendBuffer[12] = 0x00;
sendBuffer[13] = 0x00;
sendBuffer[14] = cmdType;
sendBuffer[15] = ackType;
memcpy(sendBuffer + 14, "ATA_UPDATE", 10);
for (i = 0; i < 100; i++)
{
printf(" %02x", sendBuffer[i]);
}
writeNum = sendto(socketId, sendBuffer, 100, 0, &sa, sizeof sa);
printf(" == write %d \n", writeNum);
if (writeNum == -1)
perror("sendto");
return TRUE;
}
/*
BOOL BlockWaitForUpdate()
{
fd_set readFd;
struct timeval tv;
int ret;
SendData(CMD_BROADCAST, NO_NEED_ACK);
while(1)
{
tv.tv_sec = 5;
tv.tv_usec = 0;
FD_ZERO(&readFd);
FD_SET(socketId, &readFd);
ret = select(socketId + 1, &readFd, NULL, NULL, &tv);
switch(ret)
{
case -1:
break;
case 0:
printf("resend data\n");
SendData(CMD_BROADCAST, NO_NEED_ACK);
break;
default:
if (FD_ISSET(socketId, &readFd))
{
printf("here come pkg\n");
if (isRightPkg(socketId, CMD_UPDATE))
{
return TRUE;
}
}
}
}
}
//这里还可以进行一些字符内容的设定,增加安全性
BOOL isRightData(char *recvBuff, UINT8 cmdType)
{
if (recvBuff == NULL)
return FALSE;
if (recvBuff[0] == cmdType)
return TRUE;
return FALSE;
}
BOOL isRightPkg(int sockfd, UINT8 cmdType)
{
int recvNum;
char recvBuff[1024];
struct sockaddr_in addr;
int addrLen = sizeof(struct sockaddr_in);
memset(&addr, 0, sizeof(addr));
recvNum = recvfrom(sockfd, recvBuff, sizeof(recvBuff),
0, (struct sockaddr*)&addr, &addrLen);
if (recvNum < 0)
{
return FALSE;
}
recvBuff[recvNum] = 0;
printf("receive from [%s] : %s\n", inet_ntoa(addr.sin_addr), recvBuff);
if (!isSetServerAddr)
{
if ((cmdType == CMD_UPDATE)
&&( isRightData(recvBuff, CMD_UPDATE)))
{
isSetServerAddr = TRUE;
serverAddress = addr;
printf("address is set: %s\n", inet_ntoa(serverAddress.sin_addr));
SendData(CMD_ACK, ACK_FOR_UPDATE);
printf("\n\n---after here begin to data transfer........\n");
return TRUE;
}
else
return FALSE;
}
else
{
if ((addr.sin_addr.s_addr == serverAddress.sin_addr.s_addr)
&& (isRightData(recvBuff, cmdType)))
{
return TRUE;
}
}
return FALSE;
}
void RecvData(int sockfd)
{
int recvNum;
char recvBuff[1024];
struct sockaddr_in addr;
int addrLen = sizeof(struct sockaddr_in);
memset(&addr, 0, sizeof(addr));
while (1)
{
recvNum = recvfrom(sockfd, recvBuff, sizeof(recvBuff),
0, (struct sockaddr*)&addr, &addrLen);
if (recvNum < 0)
{
printf("error with recv data\n");
continue;
}
recvBuff[recvNum] = 0;
PrintPkgAndAddr(PKG_RECV, addr, recvBuff, recvNum);
//here process the pkg
//PutDataInPkg();
//CheckRecvPkg();
//send ack back
if (recvBuff[0] == CMD_DATA)
{
SendData(CMD_ACK, ACK_FOR_DATA);
}
else
{
}
}
}
void PrintPkgAndAddr(PKG_TYPE type, struct sockaddr_in addr, char *buff, int len)
{
int i;
if (type == PKG_SEND)
{
printf("\nsend [%d bytes] to [%s] : ", len, inet_ntoa(addr.sin_addr));
}
else if (type == PKG_RECV)
{
printf("\nrecv [%d bytes] from [%s] : ", len, inet_ntoa(addr.sin_addr));
}
for (i = 0; i < len; i++)
{
printf(" %02x ", (unsigned char)buff[i]);
}
printf("\n");
}
void PutDataInPkg(char *buff)
{
}
BOOL CheckRecvPkg(APP_PKG *pkg)
{
}
*/
posted @
2007-11-26 19:18 吴剑 阅读(255) |
评论 (0) |
编辑 收藏
预编译一般用来防止头文件的重复包含和编译。
在我们用C做开发的是候,有时候项目很大,我们所编写的程序会很长。这样我们如果还是写在一个文件中会出现管理上的问题和
查看上的不方便。因此,我们可以分多个文件编写我们的程序,这样把一个功能的程序写到一个文件里,便于查看 也有助于我们管理。
如main.c sd.h sd.c lcd.h lcd.c fat.h fat.c delay.h delay.c
至于头文件(sd.h)和源文件(sd.c)的写法应该是这样的:
在头文件中 我们写的是 函数的声明,和一些使用到的变量的声明;
在源文件中 我们写的是 对应于头文件中的函数的具体实现,即函数左大括号和右大括号中的内容。
在具体使用相关函数是可以使用#include 命令来包含其函数所在的头文件。下面我们虚拟如下的一个程序结构来展开说明:
如在main.c中
#include delay.h
#include sd.h
#include lcd.h
#include fat.h
main()
{
sd_init();
delay_nms(10);
lcd_init();
while(1)
{
....
....;
}
}
在sd.c中
void sd_init()
{
......
......;
}
在sd.h中
#include delay.h
#include .......
void sd_init();
.....
在delay.c中
void delay_nms(UINT n)
{
for(....)
{...
_asm_("nop");
...
}
}
在delay.h中
void delay_nms(UINT n);
.....
我们看,如果在头文件的书写中都没加预编译指令
#ifndef XXXXXXXX
#define XXXXXXXX
....
....
(头文件内容)
....
#endif
会出现怎么样的情况???? 其实这样,编译会报错。编译器会提示 void delay_nms()定义了两次。为什么会出现这种情况呢?
我们顺着程序读一下,当我们在main.c中,编译器读到#include delay.h时会把 delay.h文件中的内容包含进来,这样delay.h中的
delay_nms()函数定义了一次,当我们在往下,读到#include sd.h时,把sd.h包含进来,在把sd.h的内容展开,我们又读到了一次delay.h
这样又把delay.h的内容包含近了main.c 。因此,我们发现delay.h被main.c重复包含了2次。这样使得void delay_nms()函数也被定义了二次。
如何解决重复定义的问题,我们是用条件编译,#ifndef ..等。在头文件中加入条件编译指令,如下:
在sd.h中
#ifndef _SD_H_
#define _SD_H_
#include delay.h
#include .......
void sd_init();
.....
#endif
在delay.h中
#ifndef _DELAY_H_
#define _DELAY_H_
void delay_nms(UINT n);
.....
#endif
这样,让我们再看下会出现什么情况。
当我们在main.c中,编译器读到#include delay.h时会把 delay.h文件中的内容包含进来,(#ifndef _DELAY_H_)我们先判断有没有定义
了 _DELAY_H_ 这个宏,由于这是第一次调用delay.h,_DELAY_H_这个宏还没定义,这样编译器会顺序向下执行,执行到#define _DELAY_H_
则定义了一个_DELAY_H_宏,宏值为NULL. 当我们main.c中再往下,读到#include sd.h时,把sd.h包含进来,在把sd.h的内容展开,
我们又读到了一次delay.h。 这一次 我们判断 #ifndef _DELAY_H_ 结果是已经定义了,这样,编译器会跳过下面的内容,直接到 #endif。
可见,我们的函数 void delay_nms(UINT n); 就不会被重复定义二次! 对于其他的头文件,我们现在也加上 预编译指令,防止被重复包含。
另外,在WinAVR中,如果使用多文件编译,需要在Makeflie中修改 SRC = $(TARGET).c 在其后面添加你的头文件的源文件的名字。如
SRC = $(TARGET).c delay.c sd.c fat.c lcd.h
希望以上内容能对初学者有所帮助,有什么不对的地方也恳请大家指出修正。
posted @
2007-11-11 22:20 吴剑 阅读(1565) |
评论 (0) |
编辑 收藏
_init关键字不是gcc的, 而是linux内核的。 __init, __initdata等属性标志, 是要把这种属性的代码放入目标文件的.init.text节, 数据放入.init.data节──这一过程是通过编译内核时为相关目标平台提供了xxx.lds链接脚本, 来指导ld完成的。 对i386来说, 可以参考arch/i386/kernel/vmlinux.lds.S文件。
对编译成module的代码和数据来说, 当模块加载时, __init属性的函数就被执行;
对静态编入内核的代码和数据来说, 当内核引导时, do_basic_setup()函数调用do_initcalls()函数, 后者负责所有.init节函数的执行。
__init说明这个函数仅在初始化的时候使用,在模块挂载以后,就会把初始化函数扔掉,可以把该函数占用的内存扔掉,
posted @
2007-11-06 18:36 吴剑 阅读(462) |
评论 (0) |
编辑 收藏
// File: prg6_9.c
#include <stdio.h> /* These are the usual header files */
#include <strings.h> /* for bzero() */
#include <unistd.h> /* for close() */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <stdlib.h>
#define PORT 1234 /* Port that will be opened */
#define BACKLOG 5 /* Number of allowed connections */
#define MAXDATASIZE 1000
typedef struct CLIENT{
int fd;
char* name;
struct sockaddr_in addr; /* client's address information */
char* data;
};
void process_cli(CLIENT *client, char* recvbuf, int len);
void savedata(char* recvbuf, int len, char* data);
main()
{
int i, maxi, maxfd,sockfd;
int nready;
ssize_t n;
fd_set rset, allset;
int listenfd, connectfd; /* socket descriptors */
struct sockaddr_in server; /* server's address information */
/* client's information */
CLIENT client[FD_SETSIZE];
char recvbuf[MAXDATASIZE];
int sin_size;
/* Create TCP socket */
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
/* handle exception */
perror("Creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr = htonl (INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
/* handle exception */
perror("Bind error.");
exit(1);
}
if(listen(listenfd,BACKLOG) == -1){ /* calls listen() */
perror("listen() error\n");
exit(1);
}
sin_size=sizeof(struct sockaddr_in);
/*initialize for select */
maxfd = listenfd;
maxi = -1;
for (i = 0; i < FD_SETSIZE; i++) {
client[i].fd = -1;
}
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
while(1)
{
struct sockaddr_in addr;
rset = allset;
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &rset)) { /* new client connection */
/* Accept connection */
if ((connectfd = accept(listenfd,(struct sockaddr *)&addr,&sin_size))==-1) {
perror("accept() error\n");
continue;
}
/* Put new fd to client */
for (i = 0; i < FD_SETSIZE; i++)
if (client[i].fd < 0) {
client[i].fd = connectfd; /* save descriptor */
client[i].name = new char[MAXDATASIZE];
client[i].addr = addr;
client[i].data = new char[MAXDATASIZE];
client[i].name[0] = '\0';
client[i].data[0] = '\0';
printf("You got a connection from %s. ",inet_ntoa(client[i].addr.sin_addr) );
break;
}
if (i == FD_SETSIZE) printf("too many clients\n");
FD_SET(connectfd, &allset); /* add new descriptor to set */
if (connectfd > maxfd) maxfd = connectfd;
if (i > maxi) maxi = i;
if (--nready <= 0) continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i].fd) < 0) continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = recv(sockfd, recvbuf, MAXDATASIZE,0)) == 0) {
/*connection closed by client */
close(sockfd);
printf("Client( %s ) closed connection. User's data: %s\n",client[i].name,client[i].data);
FD_CLR(sockfd, &allset);
client[i].fd = -1;
delete client[i].name;
delete client[i].data;
} else
process_cli(&client[i], recvbuf, n);
if (--nready <= 0) break; /* no more readable descriptors */
}
}
}
close(listenfd); /* close listenfd */
}
void process_cli(CLIENT *client, char* recvbuf, int len)
{
char sendbuf[MAXDATASIZE];
recvbuf[len-1] = '\0';
if (strlen(client->name) == 0) {
/* Got client's name from client */
memcpy(client->name,recvbuf, len);
printf("Client's name is %s.\n",client->name);
return;
}
/* save client's data */
printf("Received client( %s ) message: %s\n",client->name, recvbuf);
/* save user's data */
savedata(recvbuf,len, client->data);
/* reverse usr's data */
for (int i1 = 0; i1 < len - 1; i1++) {
sendbuf[i1] = recvbuf[len - i1 -2];
}
sendbuf[len - 1] = '\0';
send(client->fd,sendbuf,strlen(sendbuf),0);
}
void savedata(char* recvbuf, int len, char* data)
{
int start = strlen(data);
for (int i = 0; i < len; i++) {
data[start + i] = recvbuf[i];
}
}
posted @
2007-11-05 22:10 吴剑 阅读(506) |
评论 (0) |
编辑 收藏
1、概述
1) 链路层,有时也称作数据链路层或网络接口层,(硬件接口,ARP,RARP)
2) 网络层,有时也称作互联网层,(IP, ICMP, IGMP)I C M P是I P协议的附属协议,I P层用它来与其他主机或路由器交换错误报文和其他重要信息。P i n g和Tr a c e r o u t e它们都使用了I C M P
3) 运输层主要为两台主机上的应用程序提供端到端的通信。(TCP, UDP)
4) 应用层负责处理特定的应用程序细节。
一个互连网就是一组通过相同协议族互连在一起的网络。
构造互连网最简单的方法是把两个或多个网络通过路由器进行连接。它是一种特殊的用
于网络互连的硬件盒。路由器的好处是为不同类型的物理网络提供连接:以太网、令牌环网、
点对点的链接和F D D I(光纤分布式数据接口)等等。
连接网络的另一个途径是使用网桥。网桥是在链路层上对网络进行互连,而路由器则是
在网络层上对网络进行互连。网桥使得多个局域网( L A N)组合在一起,这样对上层来说就好像是一个局域网。
有三类I P地址:单播地址(目的为单个主机)、广播地址(目的端为给定网络上的所有主
机)以及多播地址(目的端为同一组内的所有主机)。在T C P / I P领域中,域名系统(D N S)是一个分布的数据库,由它来提供 I P地址和主机名之间的映射信息。
T C P传给I P的数据单元称作 T C P报文段或简称为T C P段(T C P
s e g m e n t)。I P传给网络接口层的数据单元称作I P数据报(IP datagram)。通过以太网传输的比特流称作帧(Fr a m e )。
由于T C P、U D P、I C M P和I G M P都要向I P传送数据,因此I P必须在
生成的I P首部中加入某种标识,以表明数据属于哪一层。为此, I P在首部中存入一个长度为8 b i t的数值,称作协议域。1表示为I C M P协议,2表示为I G M P协议,6表示为T C P协议,1 7表示为U D P协议。
类似地,许多应用程序都可以使用 T C P或U D P来传送数据。运输层协议在生成报文首部时要存入一个应用程序的标识符。 T C P和U D P都用一个1 6 b i t的端口号来表示不同的应用程序。T C P和U D P把源端口号和目的端口号分别存入报文首部中。
网络接口分别要发送和接收I P、A R P和R A R P数据,因此也必须在以太网的帧首部中加入某种形式的标识,以指明生成数据的网络层协议。为此,以太网的帧首部也有一个 16 bit的帧类型域。
为协议I C M P和I G M P定位一直是一件很棘手的事情。在图1 - 4中,把它们与I P放在
同一层上,那是因为事实上它们是I P的附属协议。但是在这里,我们又把它们放在I P层
的上面,这是因为ICMP和IGMP报文都被封装在IP数据报中。
对于A R P和R A R P,我们也遇到类似的难题。在这里把它们放在以太网设备驱动程
序的上方,这是因为它们和I P数据报一样,都有各自的以太网数据帧类型。但在图2 - 4
中,我们又把A R P作为以太网设备驱动程序的一部分,放在I P层的下面,其原因在逻
辑上是合理的。
2、链路层
在T C P / I P协议族中,链路层主要有三个目的:(1)为I P模块发送和接收I P数据报;(2)为A R P模块发送A R P请求和接收A R P应答;(3)为R A R P发送R A R P请求和接收R A R P应答。T C P / I P支持多种不同的链路层协议,这取决于网络所使用的硬件,如以太网、令牌环网、F D D I(光纤分布式数据接口)及R S-2 3 2串行线路等。
以太网和IEEE 802封装: 当今 T C P / I P采用的主要的局域网技术。它采用一种称作 C S M A / C D的媒体接入方法,其意思是带冲突检测的载波侦听多路接入(Carrier Sense, Multiple Access with Collision Detection)。它的速率为10 Mb/s,地址为48 bit。I E E E(电子电气工程师协会) 8 0 2委员会公布了一个稍有不同的标准集。
S L I P的全称是Serial Line IP。它是一种在串行线路上对I P数据报进行封装的简单形式
P P P,点对点协议修改了S L I P协议中的所有缺陷。P P P包括以下三个部分:
1) 在串行链路上封装 I P数据报的方法。 P P P既支持数据为8位和无奇偶检验的异步模式大多数计算机上都普遍存在的串行接口),还支持面向比特的同步链接。
2) 建立、配置及测试数据链路的链路控制协议( L C P:Link Control Protocol)。它允许通
方进行协商,以确定不同的选项。
3) 针对不同网络层协议的网络控制协议( N C P:Network Control Protocol)体系。当前定义的网络层有I P、O S I网络层、D E C n e t以及A p p l e Ta l k。例如,IP NCP允许双方商定是报文首部进行压缩,类似于C S L I P(缩写词N C P也可用在T C P的前面)。
大多数的产品都支持环回接口(Loopback Interface),以允许运行在同一台主机上的客户
程序和服务器程序通过 T C P / I P进行通信。一旦传输层检测到目的端地址是环回地址时,应该可以省略部分传输层和所有网络层的逻辑操作。但是大多数的产品还是照样完成传输层和网络层的所有过程,只是当I P数据报离开网络层时把它返回给自己。正因为不经过链路层,所以这种数据包不会出现在网络上。
以太网和8 0 2 . 3对数据帧的长度都有一个限制,其最大值分别是1 5 0 0和1 4 9 2字节。链路层的这个特性称作MTU字节网络M T U,最大传输单元。
T C P / I P成功的原因之一是它几乎能在任何数据链路技术上运行。
3、I P是T C P / I P协议族中最为核心的协议。所有的T C P、U D P、I C M P及I G M P数据都以I P数据报格式传输。
不可靠(u n r e l i a b l e)的意思是它不能保证 I P数据报能成功地到达目的地。 I P仅提供最好的传输服务。如果发生某种错误时,如某个路由器暂时用完了缓冲区, I P有一个简单的错误处理算法:丢弃该数据报,然后发送 I C M P消息报给信源端。任何要求的可靠性必须由上层来提供(如T C P)。
无连接(c o n n e c t i o n l e s s)这个术语的意思是I P并不维护任何关于后续数据报的状态信息。每个数据报的处理是相互独立的。这也说明, I P数据报可以不按发送顺序接收。如果一信源向相同的信宿发送两个连续的数据报(先是 A,然后是B),每个数据报都是独立地进行路由选择,可能选择不同的路线,因此B可能在A到达之前先到达。
为了计算一份数据报的 I P检验和,首先把检验和字段置为 0。然后,对首部中每个 16 bit
进行二进制反码求和(整个首部看成是由一串 16 bit的字组成),结果存在检验和字段中。当收到一份I P数据报后,同样对首部中每个16 bit进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全 1。如果结果不是全1(即检验和错误),那么I P就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。I P路由选择是简单的,特别对于主机来说。如果目的主机与源主机直接相连(如点对点链路)或都在一个共享网络上(以太网或令牌环网),那么I P数据报就直接送到目的主机上。否则,主机把数据报发往一默认的路由器上,由路由器来转发该数据报。大多数的主机都是采用这种简单机制。即 I P层既可以配置成路由器的功能,也可以配置成主机的功能。当今的大多数多用户系统,包括几乎所有的 U n i x系统,都可以配置成一个路由器。我们可以为它指定主机和路由器都可以使用的简单路由算法。本质上的区别在于主机从不把数据报从一个接口转发到另一个接口,而路由器则要转发数据报。内含路由器功能的主机应该从不转发数据报,除非它被设置成那样。在 9 . 4小节中,我们将进一步讨论配置的有关问题。
在一般的体制中,I P可以从T C P、U D P、I C M P和I G M P接收数据报(即在本地生成的数据报)并进行发送,或者从一个网络接口接收数据报(待转发的数据报)并进行发送。 I P层在内存中有一个路由表。当收到一份数据报并进行发送时,它都要对该表搜索一次。当数据报来自某个网络接口时,I P首先检查目的I P地址是否为本机的I P地址之一或者I P广播地址。如果确实是这样,数据报就被送到由 I P首部协议字段所指定的协议模块进行处理。如果数据报的目的不是这些地址,那么( 1)如果I P层被设置为路由器的功能,那么就对数据报进行转发(也就是说,像下面对待发出的数据报一样处理);否则( 2)数据报被丢弃。
路由表中的每一项都包含下面这些信息:
* 目的I P地址。它既可以是一个完整的主机地址,也可以是一个网络地址,由该表目中的标志字段来指定(如下所述)。主机地址有一个非0的主机号(见图1 - 5),以指定某一特定的主机,而网络地址中的主机号为0,以指定网络中的所有主机(如以太网,令牌环网)。
* 下一站(或下一跳)路由器( next-hop router)的I P地址,或者有直接连接的网络 I P地址。下一站路由器是指一个在直接相连网络上的路由器,通过它可以转发数据报。下
一站路由器不是最终的目的,但是它可以把传送给它的数据报转发到最终目的。
* 标志。其中一个标志指明目的 I P地址是网络地址还是主机地址,另一个标志指明下一
站路由器是否为真正的下一站路由器,还是一个直接相连的接口(我们将在 9 . 2节中
详细介绍这些标志)。
为数据报的传输指定一个网络接口。
I P路由选择是逐跳地(h o p - b y - h o p )进行的。从这个路由表信息可以看出,I P并不知道到 达任何目的的完整路径(当然,除了那些与主机直接相连的目的)。所有的I P路由选择只为数 据报传输提供下一站路由器的 I P地址。它假定下一站路由器比发送数据报的主机更接近目的, 而且下一站路由器与该主机是直接相连的。
I P路由选择主要完成以下这些功能:
1) 搜索路由表,寻找能与目的I P地址完全匹配的表目(网络号和主机号都要匹配)。如果找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标
志字段的值)。
2) 搜索路由表,寻找能与目的网络号相匹配的表目。如果找到,则把报文发送给该表目指定的下一站路由器或直接连接的网络接口(取决于标志字段的值)。目的网络上的所有主机都可以通过这个表目来处置。例如,一个以太网上的所有主机都是通过这种表目进行寻径的。这种搜索网络的匹配方法必须考虑可能的子网掩码。关于这一点我们在下一节中进行
讨论。
3) 搜索路由表,寻找标为“默认( d e f a u l t )”的表目。如果找到,则把报文发送给该表目指定的下一站路由器。
如果上面这些步骤都没有成功,那么该数据报就不能被传送。如果不能传送的数据报来自本机,那么一般会向生成数据报的应用程序返回一个“主机不可达”或“网络不可达”的错误。
给定I P地址和子网掩码以后,主机就可以确定 I P数据报的目的是:(1)本子网上的主机;
(2)本网络中其他子网中的主机;( 3)其他网络上的主机。如果知道本机的 I P地址,那么就知道它是否为A类、B类或C类地址(从I P地址的高位可以得知),也就知道网络号和子网号之间的分界线。而根据子网掩码就可知道子网号与主机号之间的分界线。
举例
假设我们的主机地址是1 4 0 . 2 5 2 . 1 . 1(一个B类地址),而子网掩码为2 5 5 . 2 5 5 . 2 5 5 . 0(其中8b i t为子网号,8 bit为主机号)。* 如果目的I P地址是1 4 0 . 2 5 2 . 4 . 5,那么我们就知道B类网络号是相同的(1 4 0 . 2 5 2),但是子网号是不同的(1和4)。用子网掩码在两个I P地址之间的比较如图3 - 8所示。* 如果目的I P地址是1 4 0 . 2 5 2 . 1 . 2 2,那么B类网络号还是一样的(1 4 0 . 2 5 2),而且子网号也是一样的(1),但是主机号是不同的。
如果目的I P地址是1 9 2 . 4 3 . 2 3 5 . 6(一个C类地址),那么网络号是不同的,因而进一步的比较就不用再进行了。
posted @
2007-10-25 19:43 吴剑 阅读(464) |
评论 (0) |
编辑 收藏
6、ICMP: Internet控制报文协议
I C M P报文通常被I P层或更高层协议( T C P或U D P)使用。I C M P报文是在I P数据报内部被传输的
ICMP不同类型由报文中的类型字段和代码字段来共同决定。可以确定I C M P报文是一份查询报文还是一份差错报文。因为对I C M P差错报
文有时需要作特殊处理,因此我们需要对它们进行区分。例如,在对I C M P差错报文进行响应
时,永远不会生成另一份I C M P差错报文(如果没有这个限制规则,可能会遇到一个差错产生
另一个差错的情况,而差错再产生差错,这样会无休止地循环下去)。当发送一份I C M P差错报文时,报文始终包含I P的首部和产生I C M P差错报文的I P数据报的前8个字节。这样,接收I C M P差错报文的模块就会把它与某个特定的协议(根据I P数据报首部中的协议字段来判断)和用户进程(根据包含在I P数据报前8个字节中的T C P或U D P报文首部中的T C P或U D P端口号来判断)联系起来。
7、ping程序
发送回显请求的p i n g程序为客户,而称被p i n g的主机为服务器。大多数的T C P / I P
实现都在内核中直接支持P i n g服务器—这种服务器不是一个用户进程(在第6章中描述的两
种I C M P查询服务,地址掩码和时间戳请求,也都是直接在内核中进行处理的)
I C M P回显请求和回显应答报文如图7 - 1所示。
对于其他类型的I C M P查询报文,服务器必须响应标识符和序列号字段。另外,客户发送
的选项数据必须回显,假设客户对这些信息都会感兴趣。
U n i x系统在实现p i n g程序时是把I C M P报文中的标识符字段置成发送进程的I D号。这样
即使在同一台主机上同时运行了多个p i n g程序实例, p i n g程序也可以识别出返回的信息。序列号从0开始,每发送一次新的回显请求就加1。p i n g程序打印出返回的每个分组的序列号,允许我们查看是否有分组丢失、失序或重复。I P是一种最好的数据报传递服务,因此
这三个条件都有可能发生。
敲入p i n g命令,几秒钟过后会在第1行打印出I P地址, D N S就是利用这段时间来确定主机
名所对应的I P地址。通常,第1个往返时间值要比其他的大。这是由于目的端的硬件地址不在A R P高速缓存中的缘故。在发送第一个回显请求之前要发送一个A R P请求并接收A R P应答,这需要花费几毫秒的时间。
IP首部最长为6 0个字节, 固定长度为2 0字节,R R选项用去3个字节(下面我们再讨论),这样只剩下3 7个字节( 6 0-2 0-3)来存放I P地址清单,也就是说只能存放9个I P地址。I P数据报中的R R选项的一般格式如图所示。
c o d e是一个字节,指明I P选项的类型。对于R R选项来说,它的值为7。l e n是R R选项总字
节长度,在这种情况下为3 9(尽管可以为R R选项设置比最大长度小的长度,但是p
i n g程序
总是提供3 9字节的选项字段,最多可以记录9个I P地址。由于I P首部中留给选项的空间有限,
它一般情况都设置成最大长度)。
p t r称作指针字段。它是一个基于1的指针,指向存放下一个I P地址的位置。它的最小值为
4,指向存放第一个I P地址的位置。随着每个I P地址存入清单, p t r的值分别为8,1 2,1 6,最
大到3 6。当记录下9个I P地址后,p t r的值为4 0,表示清单已满。
I P时间戳选项与记录路由选项类似。
8、Traceroute程序
在7 . 3节中,我们描述了I P记录路由选项( R R)。为什么不使用这个选项而另外开发一个
新的应用程序?有三个方面的原因。首先,原先并不是所有的路由器都支持记录路由选项,
因此该选项在某些路径上不能使用( Tr a c e r o u t e程序不需要中间路由器具备任何特殊的或可选的功能)。
其次,记录路由一般是单向的选项。发送端设置了该选项,那么接收端不得不从收到的I P
首部中提取出所有的信息,然后全部返回给发送端。在7 . 3节中,我们看到大多数P i n g服务器的实现(内核中的I C M P回显应答功能)把接收到的R R清单返回,但是这样使得记录下来的I P地址翻了一番(一来一回)。这样做会受到一些限制,这一点我们在下一段讨论(
Tr a c e r o u t e程序只需要目的端运行一个U D P模块—其他不需要任何特殊的服务器应用程序)。
最后一个原因也是最主要的原因是, I P首部中留给选项的空间有限,不能存放当前大多
数的路径。在I P首部选项字段中最多只能存放9个I P地址。在原先的A R PA N E T中这是足够的,
但是对现在来说是远远不够的。
Tr a c e r o u t e程序使用I C M P报文和I P首部中的T T L字段(生存周期)。每个处理数据报的路由器都需要把T T L的值减1或减去数据报在路由器中停留的秒数。由于大多数的路由器转发数据报的时延都小于1秒钟,因此T T L最终成为一个跳站的计数器,所经过的每个路由器都将其值减1。T T L字段的目的是防止数据报在选路时无休止地在网络中流动。
当路由器收到一份I P数据报,如果其T T L字段是0或1,则路由器不转发该数据报(接收到
这种数据报的目的主机可以将它交给应用程序,这是因为不需要转发该数据报。但是在通常
情况下,系统不应该接收T T L字段为0的数据报)。相反,路由器将该数据报丢弃,并给信源
机发一份I C M P“超时”信息。Tr a c e r o u t e程序的关键在于包含这份I C M P信息的I P报文的信源地址是该路由器的I P地址。
我们现在可以猜想一下Tr a c e r o u t e程序的操作过程。它发送一份T T L字段为1的I P数据报给
目的主机。处理这份数据报的第一个路由器将T T L值减1,丢弃该数据报,并发回一份超时
I C M P报文。这样就得到了该路径中的第一个路由器的地址。然后Tr a c e r o u t e程序发送一份
T T L值为2的数据报,这样我们就可以得到第二个路由器的地址。继续这个过程直至该数据报
到达目的主机。但是目的主机哪怕接收到T T L值为1的I P数据报,也不会丢弃该数据报并产生
一份超时I C M P报文,这是因为数据报已经到达其最终目的地。那么我们该如何判断是否已经
到达目的主机了呢?
Tr a c e r o u t e程序发送一份U D P数据报给目的主机,但它选择一个不可能的值作为U D P端口
号(大于30 000),使目的主机的任何一个应用程序都不可能使用该端口。因为,当该数据报
到达时,将使目的主机的U D P模块产生一份“端口不可达”错误(见6 . 5节)的I C M P报文。
这样,Tr a c e r o u t e程序所要做的就是区分接收到的I C M P报文是超时还是端口不可达,以判断什么时候结束。
posted @
2007-10-24 23:09 吴剑 阅读(370) |
评论 (0) |
编辑 收藏
大家都知道数据在计算机中都是按字节来储存了,1个字节等于8位(1Byte=8bit),而计算机只能识别0和1这两个数,所以根据排列,1个字
节能代表256种不同的信息,即28(0和1两种可能,8位排列),比如定义一个字节大小的无符号整数(unsigned
char),那么它能表示的是0~255(0~28-1)这些数,一共是256个数,因为,前面说了,一个字节只能表示256种不同的信息。别停下,还是
一个字节的无符号整数,我们来进一步剖析它,0是这些数中最小的一个,我们先假设它在计算机内部就用8位二进制表示为00000000(从理论上来说也可
以表示成其他不同的二进制码,只要这256个数每个数对应的二进制码都不相同就可以了),再假设1表示为00000001,2表示为00000010,3
表示为00000011,依次类推,那么最大的那个数255在8位二进制中就表示为最大的数11111111,然后,我们把这些二进制码换算成十进制看
看,会发现刚好和我们假设的数是相同的,而事实上,在计算机中,无符号的整数就是按这个原理来储存的,所以告诉你一个无符号的整数的二进制码,你就可以知
道这个数是多少,而且知道在计算机中,这个数本身就是以这个二进制码来储存的。比如我给你一个2个字节大小的二进制码,首先声明它表示的是无符号的整数:
00000000
00000010,我们把前面的0省略,换算一下,它表示的也是数值2,和前面不同的是,它占了2个字节的内存。不同的类型占的内存空间不同,如在我的电
脑中char是1个字节,int是4个字节,long是8个字节(你的可能不同,这取决于不同的计算机设置),它们的不同之处仅仅是内存大的能表示的不同
的信息多些,也就是能表示的数范围更大些(unsigned
int能表示的范围是0~28*4-1),至于怎么算,其实都是一样的,直接把二进制与十进制相互转换,二进制就是它在计算机中的样子,十进制就是我们所
表示的数。啊哈,原来这些都是可以计算的呀,我曾经还以为不同的计算机储存的原理是不同的,取决于商家的喜好呢,呵呵。说了这么多怎么还没有提到原码、反
码和补码呀,别急别急,心急吃不了热豆腐,呵呵,因为无符号的整数根本就没有原码、反码和补码。(啊,那不是被欺骗了,5555````我告诉妈妈去,哥
哥欺负我)都说了别急嘛,你就不想想我说了这么半天的无符号整数,那么有符号的整数怎么办啊?
呵呵,对,只有有符号的整数才有原码、反
码和补码的!其他的类型一概没有。虽然我们也可以用二进制中最小的数去对应最小的负数,最大的也相对应,但是那样不科学,下面来说说科学的方法。还是说一
个字节的整数,不过这次是有符号的啦,1个字节它不管怎么样还是只能表示256个数,因为有符号所以我们就把它表示成范围:-128-127。它在计算机
中是怎么储存的呢?可以这样理解,用最高位表示符号位,如果是0表示正数,如果是1表示负数,剩下的7位用来储存数的绝对值的话,能表示27个数的绝对
值,再考虑正负两种情况,27*2还是256个数。首先定义0在计算机中储存为00000000,对于正数我们依然可以像无符号数那样换算,从
00000001到01111111依次表示1到127。那么这些数对应的二进制码就是这些数的原码。到这里很多人就会想,那负数是不是从
10000001到11111111依次表示-1到-127,那你发现没有,如果这样的话那么一共就只有255个数了,因为10000000的情况没有考
虑在内。实际上,10000000在计算机中表示最小的负整数,就是这里的-128,而且实际上并不是从10000001到11111111依次表示-1
到-127,而是刚好相反的,从10000001到11111111依次表示-127到-1。负整数在计算机中是以补码形式储存的,补码是怎么样表示的
呢,这里还要引入另一个概念——反码,所谓反码就是把负数的原码(负数的原码和和它的绝对值所对应的原码相同,简单的说就是绝对值相同的数原码相同)各个
位按位取反,是1就换成0,是0就换成1,如-1的原码是00000001,和1的原码相同,那么-1的反码就是11111110,而补码就是在反码的基
础上加1,即-1的补码是11111110+1=11111111,因此我们可以算出-1在计算机中是按11111111储存的。总结一下,计算机储存有
符号的整数时,是用该整数的补码进行储存的,0的原码、补码都是0,正数的原码、补码可以特殊理解为相同,负数的补码是它的反码加1。下面再多举几个例
子,来帮助大家理解!
十进制 → 二进制 (怎么算?要是不知道看计算机基础的书去)
47 → 101111
有符号的整数 原码 反码 补码
47 00101111 11010000 00101111(正数补码和原码相同)
-47 00101111 11010000 11010001(负数补码是在反码上加1)
再举个例子,学C语言的同学应该做过这道题:
把-1以无符号的类型输出,得什么结果?(程序如下)
#include<iostream.h>
void main()
{
short int n=-1;
cout<<(unsigned short int)n<<endl;
}
首先在我的电脑中short
int类型的储存空间是2个字节,你的可能不同,我说过,这取决于你的计算机配置。它能储存28*2=65536个不同的数据信息,如果是无符号那么它的
范围是0~65535(0~216-1),如果是有符号,那么它的范围是-32768~32767(-215~215-1)。这道题目中,开始n是一个有
符号的短整型变量,我们给它赋值为-1,根据我们前面所说的,它在计算机中是以补码11111111
11111111储存的,注意前面说了是2个字节。如果把它强制为无符号的短整型输出的话,那么我们就把刚才的二进制把看成无符号的整型在计算机中储存的
形式,对待无符号的整型就没有什么原码、反码和补码的概念了,直接把11111111
11111111转化成十进制就是65535,其实我们一看都是一就知道它是范围中最大的一个数了。呵呵,就这么简单。你个把上面的源代码编译运行看看,
如果你的电脑short int也是两个字节,那就会和我得一样的结果。你可以先用这个语句看看:cout<<sizeof(short
int)<<endl;看看你的电脑里的短整型占多少的储存空间,也可以用sizeof来看其它任何类型所分配的储存空间。
最后提醒一句,关于数据如何在计算机中储存的,这里只适用于整型的数据,对于浮点型的是另一种方式,这里我们暂时就不深究了。
posted @
2007-10-23 23:11 吴剑 阅读(435) |
评论 (0) |
编辑 收藏
//*********************************************************
//
//
//字符串转化成16进制存入CHAR数组中
// 作者:QQTCC
// 日期:2006.12.7
//
//*********************************************************
#include <iostream>
using namespace std;
bool isNumber(char *p) //字符串位是否数字或字母判断;
{
if(*p>='0'&& *p<='9')
{return true;}
else return false;
}
void convert(char *p,unsigned char str[])
{
int high,low,i=0;
while(*p!='\0')
{
if(isNumber(p))
{high=(*p-48)<<4; } //按ASCII转换成整数并左移4位
else
{high=(*p-55)<<4; } //ASCII转换成整数左移4位
high=high&0xF0; //低4位屏蔽
++p; //读低4位
if(isNumber(p))
{low=*p-48; } //左移4位
else
{low=*p-55; } //左移4位
low=low&0x0F; //高4位屏蔽
str[i++]=low|high;
++p;
}
}
void main()
{
char str[]="1234567890ABCDEF";
unsigned char dest[8];
char *p=str;
convert(p,dest);
for(int j=0;j<8;j++)
{cout<<hex<<dest[j]<<endl;} //结果扩展ASCII,我查表验证过.
}
posted @
2007-10-21 23:44 吴剑 阅读(1687) |
评论 (1) |
编辑 收藏