基本操作流程 用 usock 函数编写 TCP 客户端程序 usock_open 用 usock_open 获取一个 usock 描述符 usock_connect 用刚才返回的描述符进行 connect 操作 connect 时指定的对方地址用 usockaddr_in 结构体描述 usock_write 用 usock_write 写数据给对方, 用 usock_read 读取对方发来的数据 usock_read usock_close 完成该连接上的所有数据传输后, 用 usock_close 关闭 usock 描述符, 关闭后, 此描述符不应再被使用 usock 函数与 BSD socket 函数的对比 BSD Socket usock 函数描述 socket usock_open usock usock_open(long af, short type, short protocol); 只支持一种 af, 即 AF_INET 只支持一种 type, 即 US_STREAM 注意不要写成 SOCK_STREAM protocol 参数添成 0 返回值 : 成功返回一个大于或等于 0 的 usock 描述符 失败返回负值, 失败的原因一般是描述符用尽 usock 可以支持同时打开 12 个描述符 close usock_close int usock_close(usock s); 一般不会失败, 除非传给了错误的 s 参数 sockaddr_in 结构体 usockaddr_in 结构体 usockaddr_in 结构体用于描述台主机的 IP 地址, 存储于其中 IP 地址与端口号是以网络字节序表示的 BSD socket 中凡是用到 sockaddr_in 的地方到了 usock 上都要改用
usockaddr_in struct usockaddr_in unsigned char sin_len; unsigned char sin_family; unsigned short sin_port; struct uin_addr sin_addr; char sin_zero[8]; ; struct uin_addr unsigned long s_addr; ; bind usock_bind int usock_bind(usock s, struct usockaddr_in * addr); 在发起连接 (usock_connect) 前调用此函数, 用于指定本端所用的 IP 地址与端口号 usockaddr_in 的 sin_addr 成员可以指定为 0, 或本机实际的 IP 地址, 这样 usock 将使用本机的实际 IP 地址, 其他的值将导致后续的 usock_connect 失败 返回值 : 一般不会失败, 即返回 0 connect usock_connect int usock_connect(usock s, const struct usockaddr_in * addr, unsigned char tos, unsigned int timeout); tos 参数, 无用处, 应指定为 0 timeout 参数 : 指定 connect 操作的超时值, 以毫秒为单位, 必须用大于 0 的数值 在此时间内成功建立连接则返回 0; 未成功建立连接则返回负值错误码, 一般为 TCPERR_TIMEOUT send write writev recv read readv usock_write usock_read int usock_write(usock s, void *buf, unsigned int len, unsigned int timeout); 将 buf 指向的 len 字节数据写入 socket, 超时值用 timeout 指示 ( 以毫秒为单位, 必须大于 0) 大于等于 0 的返回值指示写入了多少字节 负返回值指示错误码 int usock_read(usock s, void *buf, unsigned int len, unsigned int timeout); usock_read 试图从 socket 中读出 len, 存入 buf 指向的缓冲区中, 超时值由 timeout 指定 ( 以毫秒为单位, 必须大于 0) 若提供的参数合法,usock_read 将在以下三种情况下返回 1. 读到 len 字节 2. 收到对方 TCP 发来的 FIN 或 RST 3. 超时 此情况下 usock_read 的返回值可能在 0~ len 之间
特别注意 : 只收到一个对方的 TCP 报文并不会使 usock_read 返回, 这是不同于 BSD socket 的地方 usock_read 的这个特点在使用上可能会带来不便 返回值 : 返回 0 或正值表示读到的字节数 返回 TCPERR_EOF 表示收到了对方的 FIN 返回 TCPERR_RESET 表示收到了对方的 RST 返回其他的负值表示一种错误 select getsockname getpeername setsockopt getsockopt ntohs, ntohl htons, htonl 无 无 NetGetHostIPAddr usock_set_opt usock_get_opt ntohs, ntohl htons, htonl int NetGetHostIPAddr(unsigned long *phostip, char szhostip[16]); 仅仅用于获取本机的 IP 地址 phostip 与 szhostip 都是可选参数, phostip 用于获取主机字节序的本机 IP 地址 ;szhostip 则会被存入转换为字符串的 IP 地址 ( 如 192.168.0.2 ) 在 850 未拨号的情况下, 填入的是 0 地址, 同时返回 -1 usock 底下的协议栈没有 127.0.0.1 的概念 拨号成功的情况下,pHostIP 与 szhostip 中被填入 PPP server 分配给自己的 IP 地址 int usock_set_opt(usock sock, int optname, const void *optval, int optlen); int usock_get_opt(usock sock, int optname, void *optval, int optlen); usock 的这两个函数没有 level 参数 usock 仅支持一个 socket 选项 ( 指定于 optname 参数 ): USOCKOPT_RST_ON_CLOSE 当用 usock_set_opt 设置此选项时,optval 参数本身若为非 NULL 指针, 表示在 usock_close 时将向对方发 RST, 若 optval 为 NULL, 则向对方发 FIN usock_close 时发 FIN 是默认行为 用 usock_get_opt 获取此选项时, optval 应指向调用者提供的一个 int 变量, 该变量返回 1 指示 RST 行为,0 指示 FIN 行为 usock 中这几个函数的名称及作用与 BSD socket 的相同 inet_addr inet_ntoa uinet_addr uinet_ntoa uinet_addr 的作用及函数参数与 BSD socket 的相应函数相同 uinet_ntoa 略有不同 : char * uinet_ntoa(unsigned long ip_addr_network_order, char buf[]); uinet_ntoa 将转换出的 IP 地址字符串填于用户的 buf 缓冲区中, buf 缓冲区应该至少为 16 字节 uinet_addr 的返回值即是 buf 的值
更多细节说明 usock 错误码 usock 函数底下使用的是 ucip,ucip 定义的如下负值错误码也是 usock 函数可能返回的错误码 这些错误码的部分在刚才的 usock_connect usock_read 中解释过 #define TCPERR_EOF -1 /* End of data. */ #define TCPERR_ALLOC -2 /* Unable to allocate a control block. */ #define TCPERR_PARAM -3 /* Invalid parameters. */ #define TCPERR_INVADDR -4 /* Invalid address. */ #define TCPERR_CONFIG -5 /* Invalid configuration. */ #define TCPERR_CONNECT -6 /* No connection. */ #define TCPERR_RESET -7 /* Connection reset received. */ #define TCPERR_TIMEOUT -8 /* Timeout on transmission. */ usock 不支持的东西 不支持 UDP 插口 不支持 listen accept 操作 其他 usock_open 只在 850 返回成功后才能返回有效的 usock 描述符, 这意味着你没有办法建立一个自己到自己的连接来测试 usock 的工作 当然,usock 不支持 listen accept 操作也使得你无法做到这一点 具体的例子 下面的例子演示了如何使用 usock 函数 该例子程序连接一个 IP 地址硬编码于程序中的服务器, 写入一个简单的 HTTP 请求字符串, 然后循环地读取服务器响应, 直到读取结束或超过自定义的超时时间为止 该程序还展示了以下特性 连接服务器并读取服务器数据的代码是由独立的工作线程 ( 任务 ) 完成的, 因此主线程可以应终端用户的要求对工作线程进行 打断 虽然程序安排的 usock_connect 超时时间是 12 秒, 但是用 usock_connect 连接服务器的过程却放在一个循环中进行, 循环中的每次 usock_connect 调用指定一个较短的超时值 (3 秒 ), 这样,connect 循环每 3 秒就能够返回并检查以下主线程是否设置了退出标志 usock_read 循环采用了个 usock_connect 循环类似的方法来周期性地检测退出标志 此例子要写给服务器的数据很短 ( 十几字节 ), 所以就没有为 usock_write 安排循环, 但如果要用 usock_write 写入大量数据, 则也可以用同样的循环方法, 以便接受用户的 打断 必须用循环的原因在于 : 没有办法中断一个已经在进行中的 usock_read 或 usock_write 调用 该例子由两个 cpp 文件构成,main.cpp 是演示 usock 函数的主文件,my_create_task.cpp 是对 UDeleteTask 的一个简单封装, 使得创建任务的代码会显得少些
//////////////////////// main.cpp ////////////////////// #include <ubase.h> #include <net/unet.h> #include "ubcommop.h" struct SThreadParam usockaddr_in addrpeer; int quit_flag; int end_flag; ; void go_connection(usock sock, SThreadParam *ptp) const usockaddr_in *p_addrpeer = &ptp->addrpeer; int re; time_t sec_limit = 0; printf("connecting...\n"); sec_limit = time(null)+12; // total timeout 12 seconds for(;;) if(ptp->quit_flag) printf("canceled.\n"); return; re = usock_connect(sock, p_addrpeer, 0, 3000); if(re==0) // connected break; else if(re==tcperr_timeout) if(time(null)>sec_limit) printf("connect timeout.\n"); return; // thoroughly timeout else // other error printf("connect error(%d).\n", re); return; const char *szwr = "GET / HTTP/1.1\nHost:\n\n"; int wrlen = strlen(szwr); usock_write(sock, szwr, wrlen, 1000); // 严格起见, 应该把 usock_write 写于循环中, 若 usock_write 的返回值小于 wrlen, // 还可以再次调用 usock_write 写入剩余部分
printf("reading...\n"); time_t sec_last_read = time(null); // 记录上一次读到数据的时间 int nrd = 0, totrd = 0; char rbuf[2048]; // 注意, 堆栈上的数组不要开太大 for(;;) if(ptp->quit_flag) printf("canceled.\n"); return; nrd = usock_read(sock, rbuf, sizeof(rbuf), 3000); if(nrd>0) totrd += nrd; // To-do: Copy data read(`nrd' bytes) to your own buffer... sec_last_read = time(null); else if(nrd==0) if(time(null)-sec_last_read > 12) // 如果持续 12 秒没有读到数据, 则退出 printf("read timeout.\n"); break; else // nrd<0 printf("end usock_read(%d).\n", nrd); break; printf("total read %d bytes.\n", totrd); void test_usock(sthreadparam *ptp) usock sock = usock_open(af_inet, US_STREAM, 0); if(sock<0) printf("usock_open() fail!"); return; go_connection(sock, ptp); if(ptp->quit_flag) usock_set_opt(sock, USOCKOPT_RST_ON_CLOSE, (void*)1, sizeof(int)); // This causes usock_close() to send RST instead of FIN.
usock_close(sock); printf("usock-done."); void _thread_test_usock(void *param) SThreadParam *ptp = (SThreadParam*)param; test_usock(ptp); ptp->end_flag = 1; // tell the main thread that we re done, // and can be safely killed. while(1) USleep(1000); // wait to be killed int main(void) clsrc(); SThreadParam tp = 0; tp.quit_flag = 0; tp.end_flag = 0; tp.addrpeer.sin_len = sizeof(usockaddr_in); tp.addrpeer.sin_family = AF_INET; tp.addrpeer.sin_addr.s_addr = uinet_addr("192.168.0.1"); tp.addrpeer.sin_port = htons(80); char szerr[40]; HANDLE htask = ubasecreatetask(_thread_test_usock, &tp, SYS_PRIO_USER, 16000, NULL, szerr, sizeof(szerr)); // create working thread if(htask==null) printf(szerr); USleep(1000); return 1; // If a key is press, break the working thread. if(kbdgetkey(timeout_infinite)) tp.quit_flag = 1; while(!tp.end_flag) USleep(500); UDeleteTask(hTask); printf("<end>"); KbdGetKey(TIMEOUT_INFINITE); return 0; //////////////////////// my_create_task.h ////////////////////// #ifndef my_create_task_h #define my_create_task_h
#ifdef cplusplus extern"c" #endif HANDLE ubasecreatetask(void (*pfunc)(void*), void *ptaskparam, int TaskPrio, int StackSize, const char sztaskname[k_name_length], char *errbuf, int bufsz); #ifdef cplusplus //extern"c" #endif #endif //////////////////// my_create_task.cpp ////////////////// #include <ubase.h> #include <mm_snprintf.h> #include "my_create_task.h" HANDLE ubasecreatetask(void (*pfunc)(void*), void *ptaskparam, int TaskPrio, int StackSize, const char sztaskname[k_name_length], char *errbuf, int bufsz) ST_TASKCREATE stc = 0; stc.pfunc = pfunc; stc.pparam = ptaskparam; stc.nprio = TaskPrio; stc.nstacksize = StackSize; int i; if(sztaskname) for(i=0; i<k_name_length-1; i++) stc.pname[i] = sztaskname[i]; if(sztaskname[i]=='\0') break; stc.pname[k_name_length-1] = '\0'; HANDLE htask = UCreateTask(&stc); if(htask==null && errbuf) mm_snprintf(errbuf, bufsz, "UCreateTask()fail,err=%d.", stc.nerrcode); return NULL; return htask;
History: [2004-11-10] 初稿 要求用 utools2.03.009 来编译链接例子程序 若使用 utools2.03.008 则 usock_set_opt, usock_get_opt 函数无法使用