unix网络编程进程通信 unpipc.h
https://blog.csdn.net/u010527630/article/details/33814377?spm=1001.2014.3001.5502
订阅专栏
1>解压源码unpv22e.tar.gz。
$tar zxvf unpv22e.tar.gz //这样源码就被解压到当前的目录下了
2>运行configure脚本,以生成正确的Makefile。为什么steven不直接写成Makefile,其实很简单,不同平台的Makefile不同,所以
直接写个脚本,然后在不同的平台上运行改脚本,该脚本会根据当前的平台生存正确的Makefile。
. / c o n f i g u r e / / 当前目录为源码的解压后的目录 3 > ./configure //当前目录为源码的解压后的目录 3> ./configure//当前目录为源码的解压后的目录3>cd lib
$make //编译,以生产lib文件
4>注释...再次make
5>拷贝unpipc.h和config.h到/usr/include下。同时修改unppic.h的第7行的config.h的路径为"config.h"。
拷贝libunpipc.a文件至/usr/lib和/usr/lib64下。
6>修改Make.defines。如果要使用Makefile文件的话,可以模仿steven先生的Makefile,不过他的Makefile的变量是Make.defines
里定义的,其中就有lib文件的路径,修改lib*.a文件的路为/usr/lib/lib*.a。
至此大功告成!可以像使用标准的头文件和库文件一样的使用steven的文件。使用steven的头文件并不难,难的是我不知道编译相关的知识而已。
unix 第一章 简介
1.1 概述
焦点是TCP/IP协议族,也称为网际协议族
安装程序
Unix NetWork Programming------环境搭建&第三版源码编译
c
1.(base) wannian07@wannian07-PC:~$ git clone https://github.com/maxliaops/Unix-Network-Programming.git
正克隆到 'Unix-Network-Programming'...
remote: Enumerating objects: 782, done.
remote: Total 782 (delta 0), reused 0 (delta 0), pack-reused 782
接收对象中: 100% (782/782), 579.66 KiB | 5.00 KiB/s, 完成.
处理 delta 中: 100% (115/115), 完成.
2.(base) wannian07@wannian07-PC:~$ cd Unix-Network-Programming/
3.(base) wannian07@wannian07-PC:~/Unix-Network-Programming$ chmod 755 configure
4.(base) wannian07@wannian07-PC:~/Unix-Network-Programming$ ./configure
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking for gcc... gcc
checking for C compiler default output... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ANSI C... none needed
checking for ranlib... ranlib
checking for pthread_create in -lpthread... yes
checking for t_open in -lnsl... no
checking for library containing socket... none required
checking for /usr/local/bind/lib/libbind.a... no
checking for /home/wannian07/libbind.a... no
checking for /home/wannian07/libresolv.a... no
checking for res_init in -lresolv... no
checking for t_open in -lxti... no
checking for /home/wannian07/libunp.a... no
checking for /home/wannian07/libunpxti.a... no
checking how to run the C preprocessor... gcc -E
checking for egrep... grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/socket.h... yes
checking for sys/time.h... yes
checking for time.h... yes
checking for netinet/in.h... yes
checking for arpa/inet.h... yes
checking for errno.h... yes
checking for fcntl.h... yes
checking for netdb.h... yes
checking for signal.h... yes
checking for stdio.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for sys/stat.h... yes
checking for sys/uio.h... yes
checking for unistd.h... yes
checking for sys/wait.h... yes
checking for sys/un.h... yes
checking for sys/param.h... yes
checking for sys/select.h... yes
checking for sys/sysctl.h... yes
checking for poll.h... yes
checking for sys/event.h... no
checking for strings.h... yes
checking for sys/ioctl.h... yes
checking for sys/filio.h... no
checking for sys/sockio.h... no
checking for pthread.h... yes
checking for net/if_dl.h... no
checking for xti.h... no
checking for xti_inet.h... no
checking for netconfig.h... no
checking for netdir.h... no
checking for stropts.h... yes
checking whether time.h and sys/time.h may both be included... yes
checking if uint8_t defined... yes
checking if int16_t defined... yes
checking if uint16_t defined... yes
checking if int32_t defined... yes
checking if uint32_t defined... yes
checking if size_t defined... yes
checking if ssize_t defined... yes
checking if socklen_t defined... yes
checking if sa_family_t defined... yes
checking if t_scalar_t defined... yes
checking if t_uscalar_t defined... yes
checking for struct sockaddr.sa_len... no
checking for struct sockaddr_storage... yes
checking for struct sockaddr_storage.ss_family... yes
checking for struct msghdr.msg_control... yes
checking for struct ifreq.ifr_mtu... yes
checking for getaddrinfo function prototype in netdb.h... yes
checking for getnameinfo function prototype in netdb.h... yes
checking for gethostname function prototype in unistd.h... yes
checking for getrusage function prototype in sys/resource.h... yes
checking for hstrerror function prototype in netdb.h... yes
checking for if_nametoindex function prototype in net/if.h... yes
checking for inet_aton function prototype in arpa/inet.h... yes
checking for inet_pton function prototype in arpa/inet.h... yes
checking for pselect function prototype in sys/select.h... yes
checking for snprintf function prototype in stdio.h... yes
checking for sockatmark function prototype in sys/socket.h... yes
checking for struct addrinfo... yes
checking for struct if_nameindex... yes
checking for struct sockaddr_dl... no
checking for struct timespec... yes
checking for /dev/tcp... no
checking for /dev/xti/tcp... no
checking for /dev/streams/xtiso/tcp... no
checking for bzero... yes
checking for getaddrinfo... yes
checking for gethostname... yes
checking for gethostbyname2... yes
checking for gethostbyname_r... yes
checking for getnameinfo... yes
checking for hstrerror... yes
checking for if_nametoindex... yes
checking for inet_aton... yes
checking for inet_pton... yes
checking for inet6_rth_init... yes
checking for kqueue... no
checking for kevent... no
checking for mkstemp... yes
checking for poll... yes
checking for pselect... yes
checking for snprintf... yes
checking for sockatmark... yes
checking for vsnprintf... yes
checking for IPv4 support... yes
checking for IPv6 support... yes
checking for Unix domain sockets... yes
checking for multicast support... yes
checking for -I/home/wannian07/doc/unp2ev1/src/include... no
configure: creating ./config.status
config.status: creating Makefile
config.status: creating Make.defines
config.status: creating config.h
5.(base) wannian07@wannian07-PC:~/Unix-Network-Programming$ cd lib
6.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/lib$ make
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o connect_nonb.o connect_nonb.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o connect_timeo.o connect_timeo.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o daemon_inetd.o daemon_inetd.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o daemon_init.o daemon_init.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o dg_cli.o dg_cli.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o dg_echo.o dg_echo.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o error.o error.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o get_ifi_info.o get_ifi_info.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o gf_time.o gf_time.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o host_serv.o host_serv.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o family_to_level.o family_to_level.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_leave.o mcast_leave.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_join.o mcast_join.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_get_if.o mcast_get_if.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_get_loop.o mcast_get_loop.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_get_ttl.o mcast_get_ttl.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_set_if.o mcast_set_if.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_set_loop.o mcast_set_loop.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o mcast_set_ttl.o mcast_set_ttl.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o my_addrs.o my_addrs.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o read_fd.o read_fd.c
read_fd.c: In function 'read_fd':
read_fd.c:45:3: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
*recvfd = *((int *) CMSG_DATA(cmptr));
^
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o readline.o readline.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o readn.o readn.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o readable_timeo.o readable_timeo.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o rtt.o rtt.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o signal.o signal.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o signal_intr.o signal_intr.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_bind_wild.o sock_bind_wild.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_cmp_addr.o sock_cmp_addr.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_cmp_port.o sock_cmp_port.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_ntop.o sock_ntop.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_ntop_host.o sock_ntop_host.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_get_port.o sock_get_port.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_set_addr.o sock_set_addr.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_set_port.o sock_set_port.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sock_set_wild.o sock_set_wild.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o sockfd_to_family.o sockfd_to_family.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o str_cli.o str_cli.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o str_echo.o str_echo.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o tcp_connect.o tcp_connect.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o tcp_listen.o tcp_listen.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o tv_sub.o tv_sub.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o udp_client.o udp_client.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o udp_connect.o udp_connect.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o udp_server.o udp_server.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o wraplib.o wraplib.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o wrapsock.o wrapsock.c
wrapsock.c: In function 'Inet6_rth_space':
wrapsock.c:81:8: warning: implicit declaration of function 'inet6_rth_space' [-Wimplicit-function-declaration]
ret = inet6_rth_space(type, segments);
^~~~~~~~~~~~~~~
wrapsock.c: In function 'Inet6_rth_init':
wrapsock.c:93:8: warning: implicit declaration of function 'inet6_rth_init' [-Wimplicit-function-declaration]
ret = inet6_rth_init(rthbuf, rthlen, type, segments);
^~~~~~~~~~~~~~
wrapsock.c:93:6: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
ret = inet6_rth_init(rthbuf, rthlen, type, segments);
^
wrapsock.c: In function 'Inet6_rth_add':
wrapsock.c:103:6: warning: implicit declaration of function 'inet6_rth_add' [-Wimplicit-function-declaration]
if (inet6_rth_add(rthbuf, addr) < 0)
^~~~~~~~~~~~~
wrapsock.c: In function 'Inet6_rth_reverse':
wrapsock.c:110:6: warning: implicit declaration of function 'inet6_rth_reverse' [-Wimplicit-function-declaration]
if (inet6_rth_reverse(in, out) < 0)
^~~~~~~~~~~~~~~~~
wrapsock.c: In function 'Inet6_rth_segments':
wrapsock.c:119:8: warning: implicit declaration of function 'inet6_rth_segments' [-Wimplicit-function-declaration]
ret = inet6_rth_segments(rthbuf);
^~~~~~~~~~~~~~~~~~
wrapsock.c: In function 'Inet6_rth_getaddr':
wrapsock.c:131:8: warning: implicit declaration of function 'inet6_rth_getaddr' [-Wimplicit-function-declaration]
ret = inet6_rth_getaddr(rthbuf, idx);
^~~~~~~~~~~~~~~~~
wrapsock.c:131:6: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
ret = inet6_rth_getaddr(rthbuf, idx);
^
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o wrapstdio.o wrapstdio.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o wrappthread.o wrappthread.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o wrapunix.o wrapunix.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o write_fd.o write_fd.c
write_fd.c: In function 'write_fd':
write_fd.c:24:2: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
*((int *) CMSG_DATA(cmptr)) = sendfd;
^
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o writen.o writen.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o writable_timeo.o writable_timeo.c
ar rv ../libunp.a connect_nonb.o connect_timeo.o daemon_inetd.o daemon_init.o dg_cli.o dg_echo.o error.o get_ifi_info.o gf_time.o host_serv.o family_to_level.o mcast_leave.o mcast_join.o mcast_get_if.o mcast_get_loop.o mcast_get_ttl.o mcast_set_if.o mcast_set_loop.o mcast_set_ttl.o my_addrs.o read_fd.o readline.o readn.o readable_timeo.o rtt.o signal.o signal_intr.o sock_bind_wild.o sock_cmp_addr.o sock_cmp_port.o sock_ntop.o sock_ntop_host.o sock_get_port.o sock_set_addr.o sock_set_port.o sock_set_wild.o sockfd_to_family.o str_cli.o str_echo.o tcp_connect.o tcp_listen.o tv_sub.o udp_client.o udp_connect.o udp_server.o wraplib.o wrapsock.o wrapstdio.o wrappthread.o wrapunix.o write_fd.o writen.o writable_timeo.o
ar: 正在创建 ../libunp.a
a - connect_nonb.o
a - connect_timeo.o
a - daemon_inetd.o
a - daemon_init.o
a - dg_cli.o
a - dg_echo.o
a - error.o
a - get_ifi_info.o
a - gf_time.o
a - host_serv.o
a - family_to_level.o
a - mcast_leave.o
a - mcast_join.o
a - mcast_get_if.o
a - mcast_get_loop.o
a - mcast_get_ttl.o
a - mcast_set_if.o
a - mcast_set_loop.o
a - mcast_set_ttl.o
a - my_addrs.o
a - read_fd.o
a - readline.o
a - readn.o
a - readable_timeo.o
a - rtt.o
a - signal.o
a - signal_intr.o
a - sock_bind_wild.o
a - sock_cmp_addr.o
a - sock_cmp_port.o
a - sock_ntop.o
a - sock_ntop_host.o
a - sock_get_port.o
a - sock_set_addr.o
a - sock_set_port.o
a - sock_set_wild.o
a - sockfd_to_family.o
a - str_cli.o
a - str_echo.o
a - tcp_connect.o
a - tcp_listen.o
a - tv_sub.o
a - udp_client.o
a - udp_connect.o
a - udp_server.o
a - wraplib.o
a - wrapsock.o
a - wrapstdio.o
a - wrappthread.o
a - wrapunix.o
a - write_fd.o
a - writen.o
a - writable_timeo.o
ranlib ../libunp.a
7.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/lib$ nm -g libunp.a
nm: libunp.a:无此文件
8.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/lib$ cd ../libfree/
9.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/libfree$ make
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o in_cksum.o in_cksum.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o inet_ntop.o inet_ntop.c
/usr/include/arpa/inet.h: In function 'inet_ntop':
inet_ntop.c:152:23: warning: 'best.len' may be used uninitialized in this function [-Wmaybe-uninitialized]
if (best.base == -1 || cur.len > best.len)
~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
inet_ntop.c:123:28: note: 'best.len' was declared here
struct { int base, len; } best, cur;
^~~~
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o inet_pton.o inet_pton.c
ar rv ../libunp.a in_cksum.o inet_ntop.o inet_pton.o
a - in_cksum.o
a - inet_ntop.o
a - inet_pton.o
ranlib ../libunp.a
(base) wannian07@wannian07-PC:~/Unix-Network-Programming/libfree$ cd ../libgai
(base) wannian07@wannian07-PC:~/Unix-Network-Programming/libgai$ make
ar rv ../libunp.a
ranlib ../libunp.a
10.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/libgai$ cd ..
11.(base) wannian07@wannian07-PC:~/Unix-Network-Programming$ sudo cp libunp.a /usr/lib
[sudo] wannian07 的密码:
12.(base) wannian07@wannian07-PC:~/Unix-Network-Programming$ sudo cp libunp.a /usr/lib32
13.(base) wannian07@wannian07-PC:~/Unix-Network-Programming$ cd intro/
14.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ make daytimetcpcli
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o daytimetcpcli.o daytimetcpcli.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -o daytimetcpcli daytimetcpcli.o ../libunp.a -lpthread
15.(base)wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ ./daytimetcpcli
usage: a.out <IPaddress>
16.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ ./daytimetcpcli 127.0.0.1
connect error: Connection refused
17.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ make daytimetcpsrv
gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o daytimetcpsrv.o daytimetcpsrv.c
gcc -I../lib -g -O2 -D_REENTRANT -Wall -o daytimetcpsrv daytimetcpsrv.o ../libunp.a -lpthread
18.(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ sudo ./daytimetcpsrv &
[1] 13902
19. (base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ ./daytimetcpcli 127.0.0.1
Sun Jun 2 21:18:53 2024
(base) wannian07@wannian07-PC:~/Desktop$ cd ...
(base) wannian07@wannian07-PC:~$ cd Unix-Network-Programming/
(base) wannian07@wannian07-PC:~/Unix-Network-Programming$ cd intro
测试的时候会出现如下错误connect error:Connection refused
第一种情况:没有xinetd.d,需要安装
sudo apt-get install xinetd
然后编辑daytime, wannian07@wannian07-PC# cd /etc/xinetd.d/
gedit daytime
将文件中的两个disable后面的yes改成no,保存退出
重启xinted.d
service xinetd restart
再次测试daytimetcpcli例子,
./daytimetcpcli 127.0.0.1
03 JUN 2024 18:45:28 CST
------------:https://blog.csdn.net/terence1212/article/details/51803268
c
#include "unp.h"
int main(int argc, char **argv){
union {
short s;
char c[sizeof(short)];
} un;
un.s = 0x0102;
printf("%s: ", CPU_VENDOR_OS);
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else
printf("unknown\n");
} else
printf("sizeof(short) = %d\n", sizeof(short));
exit(0);
}
//~/Unix-Network-Programming/intro/byteorder.c
wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ make byteorder
gcc -I.../lib -g -O2 -D_REENTRANT -Wall -c -o byteorder.o byteorder.c
byteorder.c: In function 'main':
byteorder.c:21:28: warning: format '%d' expects argument of type 'int', but argument 2 has type 'long unsigned int' [-Wformat=]
printf("sizeof(short) = %d\n", sizeof(short));
^
gcc -I.../lib -g -O2 -D_REENTRANT -Wall -o byteorder byteorder.o .../libunp.a -lpthread
(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ ./byteorder
x86_64-unknown-linux-gnu: little-endian
1.2 一个简单的时间获取客户程序
c
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd, n;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: a.out <IPaddress>");
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
//bzero把整个结构清零后,置地址族为AF_INET, 端口好为13
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13); //htons()主机到网络短整数
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)//inet_pton呈现形式到数值
err_quit("inet_pton error for %s", argv[1]);
//IP地址为第一个命令行参数的值 argv[1]
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
//在头文件unp.h中,我们使用#define把SA定义为struct sockaddr,也就是通用套接字地址结构。
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
if (fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if (n < 0)
err_sys("read error");
exit(0);
}
(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ make daytimetcpsrv
gcc -I.../lib -g -O2 -D_REENTRANT -Wall -c -o daytimetcpsrv.o daytimetcpsrv.c
gcc -I.../lib -g -O2 -D_REENTRANT -Wall -o daytimetcpsrv daytimetcpsrv.o .../libunp.a -lpthread
(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ sudo ./daytimetcpsrv &
[1] 13902
(base) wannian07@wannian07-PC:~/Unix-Network-Programming/intro$ ./daytimetcpcli 127.0.0.1
Sun Jun 2 21:18:53 2024
bzero不是一个ANSI C函数。它起源于早期的Berkeley网络编程代码。在整本书中使用它而不用ANSI C的memset函数,因为bzero(带2个参数)比memset(带3个参数)更好记忆。几乎所有支持套接字API的厂商都提供bzero,如果没有,那么可以使用unp.h头文件中提供的该函数的宏定义。
connect函数应用于一个TCP套接字时,将与由它的第二个参数指向的套接字地址结构指定的服务器建立一个TCP连接。该套接字地址结构的长度也必须作为该函数的第三个参数指定,对于网际套接字地址结构,我们总是使用C语言的sizeof操作符由编译器来计算这个长度。
socket一词译者认为译成"套接口"
首先,作为网络编程API之一的套接口(sockets,注意这种用法总是采用复数形式,如sockets API、sockets library等)跟XTI一样,是应用层到传输层或其他协议层的访问接口。
其次,具体使用的套接口是与Unix管道的某一端类似的东西,我们既可以往这个"口"写数据,也可以从这个"口"读数据。
最后,套接口函数使用套接口描述字(discriptor)访问具体的套接口,如果把套接口描述字的简称sockfd译成"套接字"倒比较合适。一个套接口可对应多个套接字,因为Unix的描述字既可以复制,也可以继承;反过来,一个套接字对应且只对应一个套接口。
UDP(User Datagram Protocol,用户数据报协议)
TCP(Transmission Control Protocol,传输控制协议)
1、计算机网络各层对等实体间交换的单位信息称为协议数据单元(protocol data unit,PDU)
分节(segment)就是对应于TCP传输层的PDU。
按照协议与服务之间的关系,除了最低层(物理层)外,每层的PDU通过由紧邻下层提供给本层的服务接口,作为下层的服务数据单元(service data unit,SDU)传递给下层,并由下层间接完成本层的PDU交换。
同一层内SDU作为PDU的净荷(payload)字段出现,因此可以说上层PDU由本层PDU(通过其SDU字段)承载。每层的PDU除用于承载紧邻上层的PDU(即承载数据)外,也用于承载本层协议内部通信所需的控制信息。
如果本层的PDU大小超过紧邻下层的最大SDU限制,那么本层还要事先把PDU划分成若干个合适的片段让下层分开载送,再在相反方向把这些片段重组成PDU。
2、应用层实体(如客户或服务器进程)间交换的PDU称为应用数据(application data),其中在TCP应用进程之
间交换的是没有长度限制的单个双向字节流,在UDP应用进程之间交换的是其长度不超过UDP发送缓冲区大小的
单个记录(record),在SCTP应用进程之间交换的是没有总长度限制的单个或多个双向记录流。
传输层实体(例如对应某个端口的传输层协议代码的一次运行)间交换的PDU称为消息(message),其中TCP的PDU特称为分节(segment)。消息或分节的长度是有限的。
在TCP传输层中,发送端TCP把来自应用进程的字节流数据(即由应用进程通过一次次输出操作写出到发送端TCP套接字中的数据)
按顺序经分割后封装在各个分节中传送给接收端TCP,其中每个分节所封装的数据既可能是发送端应用进程单次输出操作的结果,
也可能是连续数次输出操作的结果,而且每个分节所封装的单次输出操作的结果或者首尾两次输出操作的结果既可能是完整的,也可能是不完整的,具体取决于可在连接建立阶段由对端通告的最大分节大小(maximum segment size,MSS)以及外出接口的最大传输单元(maximum transmission unit,MTU)或外出路径的路径MTU(如果网络层具有路径MTU发现功能,如IPv6)。
分节除了用于承载应用数据外,也用于建立连接(SYN分节)、终止连接(FIN分节)、中止连接(RST
分节)、确认数据接收(ACK分节)、刷送待发数据(PSH分节)和携带紧急数据指针(URG分节),而且这些功
能(包括承载数据)可以灵活组合。
UDP传输层相当简单,发送端UDP就把来自应用进程的单个记录整个封装在UDP消息中传送给接收端UDP。 SCTP引入了称为块(chunk)的数据单元,SCTP消息就由一个公共首部加上一个或多个块构成:公共首部类似UDP消息的首部,仅仅给出源目的端口号和整个SCTP消息的校验和;
块则既可以承载数据(称为DATA块),也可以承载控制信息(计有SACK块、INIT块、INIT ACK块、COOKIE ECHO块、COOKIEACK块、SHUTDOWN块、SHUTDOWN ACK块、SHUTDOWN COMPLETE块、ABORT块、ERROR块、HEARTBEAT块和HEARTBEAT ACK块,总称为控制块)。
送端SCTP把来自应用进程的(一个或多个)记录流数据按照流内顺序和记录边界封装在各个DATA块中,并在DATA块首部记上各自的流ID。一个记录通常对应一个DATA块;
对于过长的记录,发送端SCTP既可以像UDP那样拒绝发送,也可以把它们拆分到多个DATA块中以便发送,接收端SCTP收取后把它们组合成单个记录上传。
作为传输层PDU的SCTP消息既可以只包含单个块(DATA块或控制块),也可以在接口MTU或路径MTU的限制下包含多个块(称为块的捆绑,控制块在前,DATA块在后),不过INIT块、INIT ACK块和SHUTDOWN COMPLETE块不能跟任何其他块捆绑。SCTP收发两端均独立处理捆绑在同一个消息中的各个块,鉴于此,我们可以直接把块作为传输层PDU看待,本书也往往这么使用。
3、网络层实体间交换的PDU称为IP数据报(IP datagram),其长度有限:
IPv4数据报最大65 535字节,IPv6数据报最大65 575字节。
发送端IP把来自传输层的消息(或TCP分节)整个封装在IP数据报中传送。
链路层实体间交换的PDU称为帧(frame),其长度取决于具体的接口。
IP数据报由IP首部和所承载的传输层数据(即网络层的SDU)构成。过长的IP数据报无法封装在单个帧中,需要先对其SDU进行分片(fragmentation),再把分成的各个片段(fragment)冠以新的IP首部封装到多个帧中。
在一个IP数据报从源端到目的端的传送过程中,分片操作既可能发生在源端,也可能发生在途中,而其逆操作即重组(reassembly)一般只发生在目的端;
SCTP为了传送过长的记录采取了类似的分片和重组措施。
TCP/IP协议族为提高效率会尽可能避免IP的分片/重组操作:TCP根据MSS和MTU限定每个分节的大小以及SCTP根据MTU分片/重组过长记录都是这个目的(SCTP的块捆绑则是为了在避免IP分片/重组操作的前提下提高块传输效率);
另外,IPv6禁止在途中的分片操作(基于其路径MTU发现功能),IPv4也尽量避免这种操作。
不论是否分片,都由IP作为链路层的SDU传入链路层,并由链路层封装在帧中的数据称为分组(packet,俗称包)。可见一个分组既可能是一个完整的IP数据报,也可能是某个IP数据报的SDU的一个片段被冠以新的IP首部后的结果。另外,本书中讨论的MSS是应用层(TCP)与传输层之间的接口属性,MTU则是网络层和链路层之间的接口属性。
1.3 协议无关性
c
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd, n;
struct sockaddr_in6 servaddr;
char recvline[MAXLINE + 1];
if (argc != 2)
err_quit("usage: a.out <IPaddress>");
if ( (sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin6_family = AF_INET6;
servaddr.sin6_port = htons(13); /* daytime server */
if (inet_pton(AF_INET6, argv[1], &servaddr.sin6_addr) <= 0)
err_quit("inet_pton error for %s", argv[1]);
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
if (fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if (n < 0)
err_sys("read error");
exit(0);
}
用户必须以点分十进制数格式给出服务器的IP地址(如适合于IPv4版本的206.168.112.219)。人们更习惯于用名字(如www.unpbook.com)来代替数字。
1.4 错误处理:包裹函数
既然发生错误时终止程序的运行是普遍的情况,我们可以通过定义包裹函数(wrapper function)来缩短程序。每个包裹函数完成实际的函数调用,检查返回值,并在发生错误时终止进程。我们约定包裹函数名是实际函数名的首字母大写形式。
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
中,函数Socket是函数socket的包裹函数,只要你遇到一个首字母大写的函数名,它就是我们定义的某个包裹函数。它调用的实际函数的名字与包裹函数名相同,不过以对应的小写字母开头。
1.5 一个简单的时间获取服务器程序
c
#include "unp.h"
#include <time.h>
int
main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13); /* daytime server */
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
connfd = Accept(listenfd, (SA *) NULL, NULL);
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
把套接字转换成监听套接字
调用listen函数把该套接字转换成一个监听套接字,这样来自客户的外来连接就可在该套接字上由内核接受。socket、bind和listen这3个调用步骤是任何TCP服务器准备所谓的监听描述符(listening descriptor,本例中为listenfd)的正常步骤。常值LISTENQ在我们的unp.h头文件中定义。它指定系统内核允许在这个监听描述符上排队的最大客户连接数。
接受客户连接,发送应答
通常情况下,服务器进程在accept调用中被置于休眠状态,等待某个客户连接的到达并被内核接受。TCP连接使用所谓的三路握手(three-way handshake)来建立连接。握手完毕时accept返回,其返回值是一个称为已连接描述符(connected descriptor)的新描述符(本例中为connfd)。该描述符用于与新近连接的那个客户通信。accept为每个连接到本服务器的客户返回一个新描述符。
当前时间和日期是由库函数time返回的,它实际上返回的是自Unix纪元即1970年1月1日0点0分0秒(国际标准时间)以来的秒数。下一个库函数ctime把该整数值转换成直观可读的时间格式,例如:
Mon May 26 20:58:40 2003
snprintf函数在这个字符串末尾添加一个回车符和一个回行符,随后write函数把结果字符串写给客户。调用sprintf无法检查目的缓冲区是否溢出。相反,snprintf要求其第二个参数指定目的缓冲区的大小,因此可确保该缓冲区不溢出。必须小心使用的函数还有gets、strcat和strcpy,通常应分别改为调用fgets、strncat和strncpy。更好的替代函数是后来才引入的strlcat和strlcpy,它们确保结果是正确终止的字符串。
注意。
与其客户程序一样,这一服务器程序也与IPv4协议相关。
本服务器一次只能处理一个客户。如果多个客户连接差不多同时到达,系统内核在某个最大数目的限制下把它们排入队列,然后每次返回一个给accept函数。本服务器只需调用time和ctime这两个库函数,运行速度很快。然而如果服务器需用较多时间(譬如说几秒钟或一分钟)服务每个客户,那么我们必须以某种方式重叠对各个客户的服务。所示的服务器称为迭代服务器(iterative server),因为对于每个客户它都迭代执行一次。同时能处理多个客户的并发服务器(concurrent server)有多种编写技术。最简单的技术是调用Unix的fork函数(4.7节),为每个客户创建一个子进程。其他技术包括使用线程代替fork(26.4节),或在服务器启动时预先fork一定数量的子进程(30.6节)。
如果从shell命令行启动本例这样的一个服务器,我们也许想要它运行很长时间,因为服务器往往在系统工作期间一直运行。这要求我们往服务器程序中添加代码,以便它能够作为一个Unix守护进程(daemon)---能在后台运行且不跟任何终端关联的进程---运行。
1.6 本书中客户/服务器程序示例索引表
1.7 OSI 模型
OSI模型的底下两层是随系统提供的设备驱动程序和网络硬件。通常情况下,除需知道数据链路的某些特性外(如将在2.11节论述的1500字节以太网的MTU大小)
网络层由IPv4和IPv6这两个协议处理。可以选择的传输层有TCP或UDP。图1-14中TCP与UDP之间留有间隙,表明网络应用绕过传输层直接使用IPv4或IPv6是可能的。这就是所谓的原始套接字(raw socket)。
OSI模型的顶上三层被合并成一层,称为应用层。这就是Web客户(浏览器)、Telnet客户、Web服务器、FTP服务器和其他我们在使用的网络应用所在的层。对于网际协议,OSI模型的顶上三层协议几乎没有区别。
套接字编程接口是从顶上三层(网际协议的应用层)进入传输层的接口。焦点是:如何使用套接字编写使用TCP或UDP的网络应用程序。已提到原始套接字,甚至可以彻底绕过IP层直接读写数据链路层的帧。
为什么套接字提供的是从OSI模型的顶上三层进入传输层的接口?这样设计有两个理由:
一、是顶上三层处理具体网络应用(如FTP、Telnet或HTTP)的所有细节,却对通信细节了解很少;底下四层对具体网络应用了解不多,却处理所有的通信细节:发送数据,等待确认,给无序到达的数据排序,计算并验证校验和,等等。
二、顶上三层通常构成所谓的用户进程(user process),底下四层却通常作为操作系统内核的一部分提供。Unix与其他现代操作系统都提供分隔用户进程与内核的机制。由此可见,第4层和第5层之间的接口是构建API的自然位置。
1.8 BSD 网络支持历史
1.9 测试用网络及主机
(1) netstat -i提供网络接口的信息。
(base) wannian07@wannian07-PC:~/Desktop$ netstat -ni
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
lo 65536 12174 0 0 0 12174 0 0 0 LRU
wlp1s0 1500 37 0 308 0 157 0 0 0 BMU
(base) wannian07@wannian07-PC:~/Desktop$ netstat -nr
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
1.10 Unix 标准
POSIX(可移植操作系统接口)是Portable Operating System Interface的首字母缩写。它并不是单个标准,而是由电气与电子工程师学会(the Institute for Electrical and Electronics Engineers, Inc.)即IEEE开发的一系列标准。POSIX标准已被国际标准化组织即ISO和国际电工委员会(the International Electrotechnical Commission)即IEC采纳为国际标准(这两个组织合称为ISO/IEC)。
本书的焦点是单一Unix规范第3版,其中又以套接字API为主。只要可能,我们就使用标准函数