10-socket的实践到内核--UDP的socket数据的接收
来源:互联网 发布:雪梨的淘宝店铺叫什么 编辑:程序博客网 时间:2024/05/16 01:16
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
static const struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_sock_ioctl,
#endif
.mmap = sock_mmap,
.open = sock_no_open, /* special open code to disallow open via /proc */
.release = sock_close,
.fasync = sock_fasync,
.sendpage = sock_sendpage,
.splice_write = generic_splice_sendpage,
.splice_read = sock_splice_read,
};
我是无名小卒,2008年3月开始写技术博客,博客中的内核及开发技巧的多篇文章在互联网上被转载流传,得到广泛的关注。http://qinjiana0786.cublog.cn 我们继续分析,2.6.26内核已经增加了二个钩子函数,一个是aio_read另一个是aio_write,所以从上面的结构中我们可以看出相应的write和read是实际上是这里的aio_write和aio_read,为什么会增加这二个新的钩子函数?从字面上aio可以看出应该是async io的意思,也就是说这二个钩子函数专门针对设备的异步操作所使用的,尽管我们没有解释什么能从应用程序到这里的异步操作钩子函数,以及中间从库到系统调用到这里的过程,但是我们将来肯定在文件系统的过程会走到这个过程,在那里我们会详细的分析和总结,朋友们暂且知道这里我们的write和read应用程序会执行上面二个异步操作的钩子函数,首先我们来看sock_aio_write
static ssize_t sock_aio_write(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct sock_iocb siocb, *x;
if (pos != 0)
return -ESPIPE;
x = alloc_sock_iocb(iocb, &siocb);
if (!x)
return -ENOMEM;
return do_sock_write(&x->async_msg, iocb, iocb->ki_filp, iov, nr_segs);
}
case SYS_SEND:
err = sys_send(a0, (void __user *)a1, a[2], a[3]);
asmlinkage long sys_send(int fd, void __user *buff, size_t len, unsigned flags)
{
return sys_sendto(fd, buff, len, flags, NULL, 0);
}
asmlinkage long sys_sendto(int fd, void __user *buff, size_t len,
unsigned flags, struct sockaddr __user *addr,
int addr_len)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err;
struct msghdr msg;
struct iovec iov;
int fput_needed;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
iov.iov_base = buff;
iov.iov_len = len;
msg.msg_name = NULL;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_namelen = 0;
if (addr) {
err = move_addr_to_kernel(addr, addr_len, address);
if (err < 0)
goto out_put;
msg.msg_name = address;
msg.msg_namelen = addr_len;
}
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
msg.msg_flags = flags;
err = sock_sendmsg(sock, &msg, len);
out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}
static ssize_t do_sock_write(struct msghdr *msg, struct kiocb *iocb,
struct file *file, const struct iovec *iov,
unsigned long nr_segs)
{
struct socket *sock = file->private_data;
size_t size = 0;
int i;
for (i = 0; i < nr_segs; i++)
size += iov[i].iov_len;
msg->msg_name = NULL;
msg->msg_namelen = 0;
msg->msg_control = NULL;
msg->msg_controllen = 0;
msg->msg_iov = (struct iovec *)iov;
msg->msg_iovlen = nr_segs;
msg->msg_flags = (file->f_flags & O_NONBLOCK) ? MSG_DONTWAIT : 0;
if (sock->type == SOCK_SEQPACKET)
msg->msg_flags |= MSG_EOR;
return __sock_sendmsg(iocb, sock, msg, size);
}
int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
struct kiocb iocb;
struct sock_iocb siocb;
int ret;
init_sync_kiocb(&iocb, NULL);
iocb.private = &siocb;
ret = __sock_sendmsg(&iocb, sock, msg, size);
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&iocb);
return ret;
}
static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
int err;
struct sock_iocb *si = kiocb_to_siocb(iocb);
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
si->flags = flags;
err = security_socket_recvmsg(sock, msg, size, flags);
if (err)
return err;
return sock->ops->recvmsg(iocb, sock, msg, size, flags);
}
struct msghdr {
void * msg_name; /* Socket name */
int msg_namelen; /* Length of name */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
sock->ops->recvmsg()
static const struct proto_ops unix_stream_ops
static const struct proto_ops unix_stream_ops = {
......
.recvmsg = unix_stream_recvmsg,
.....
};
asmlinkage long sys_recvmsg(int fd, struct msghdr __user *msg,
unsigned int flags)
{
struct compat_msghdr __user *msg_compat =
(struct compat_msghdr __user *)msg;
struct socket *sock;
struct iovec iovstack[UIO_FASTIOV];
struct iovec *iov = iovstack;
struct msghdr msg_sys;
unsigned long cmsg_ptr;
int err, iov_size, total_len, len;
int fput_needed;
/* kernel mode address */
char addr[MAX_SOCK_ADDR];
/* user mode address pointers */
struct sockaddr __user *uaddr;
int __user *uaddr_len;
if (MSG_CMSG_COMPAT & flags) {
if (get_compat_msghdr(&msg_sys, msg_compat))
return -EFAULT;
}
else if (copy_from_user(&msg_sys, msg, sizeof(struct msghdr)))
return -EFAULT;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out;
err = -EMSGSIZE;
if (msg_sys.msg_iovlen > UIO_MAXIOV)
goto out_put;
/* Check whether to allocate the iovec area */
err = -ENOMEM;
iov_size = msg_sys.msg_iovlen * sizeof(struct iovec);
if (msg_sys.msg_iovlen > UIO_FASTIOV) {
iov = sock_kmalloc(sock->sk, iov_size, GFP_KERNEL);
if (!iov)
goto out_put;
}
/*
* Save the user-mode address (verify_iovec will change the
* kernel msghdr to use the kernel address space)
*/
uaddr = (__force void __user *)msg_sys.msg_name;
uaddr_len = COMPAT_NAMELEN(msg);
if (MSG_CMSG_COMPAT & flags) {
err = verify_compat_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
} else
err = verify_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
if (err < 0)
goto out_freeiov;
total_len = err;
cmsg_ptr = (unsigned long)msg_sys.msg_control;
msg_sys.msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
err = sock_recvmsg(sock, &msg_sys, total_len, flags);
if (err < 0)
goto out_freeiov;
len = err;
if (uaddr != NULL) {
err = move_addr_to_user(addr, msg_sys.msg_namelen, uaddr,
uaddr_len);
if (err < 0)
goto out_freeiov;
}
err = __put_user((msg_sys.msg_flags & ~MSG_CMSG_COMPAT),
COMPAT_FLAGS(msg));
if (err)
goto out_freeiov;
if (MSG_CMSG_COMPAT & flags)
err = __put_user((unsigned long)msg_sys.msg_control - cmsg_ptr,
&msg_compat->msg_controllen);
else
err = __put_user((unsigned long)msg_sys.msg_control - cmsg_ptr,
&msg->msg_controllen);
if (err)
goto out_freeiov;
err = len;
out_freeiov:
if (iov != iovstack)
sock_kfree_s(sock->sk, iov, iov_size);
out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}
int sock_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
struct kiocb iocb;
struct sock_iocb siocb;
int ret;
init_sync_kiocb(&iocb, NULL);
iocb.private = &siocb;
ret = __sock_recvmsg(&iocb, sock, msg, size, flags);
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&iocb);
return ret;
}
static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size, int flags)
{
int err;
struct sock_iocb *si = kiocb_to_siocb(iocb);
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
si->flags = flags;
err = security_socket_recvmsg(sock, msg, size, flags);
if (err)
return err;
return sock->ops->recvmsg(iocb, sock, msg, size, flags);
}
struct sock_iocb {
struct list_head list;
int flags;
int size;
struct socket *sock;
struct sock *sk;
struct scm_cookie *scm;
struct msghdr *msg, async_msg;
struct kiocb *kiocb;
};
struct scm_cookie
{
struct ucred creds; /* Skb credentials */
struct scm_fp_list *fp; /* Passed files */
#ifdef CONFIG_SECURITY_NETWORK
u32 secid; /* Passed security ID */
#endif
unsigned long seq; /* Connection seqno */
};
struct ucred {
__u32 pid;
__u32 uid;
__u32 gid;
};
struct scm_fp_list
{
int count;
struct file *fp[SCM_MAX_FD];
};
#define SCM_MAX_FD 255
static inline struct sock_iocb *kiocb_to_siocb(struct kiocb *iocb)
{
return (struct sock_iocb *)iocb->private;
}
struct kiocb {
struct list_head ki_run_list;
unsigned long ki_flags;
int ki_users;
unsigned ki_key; /* id of this request */
struct file *ki_filp;
struct kioctx *ki_ctx; /* may be NULL for sync ops */
int (*ki_cancel)(struct kiocb *, struct io_event *);
ssize_t (*ki_retry)(struct kiocb *);
void (*ki_dtor)(struct kiocb *);
union {
void __user *user;
struct task_struct *tsk;
} ki_obj;
__u64 ki_user_data; /* user's data for completion */
wait_queue_t ki_wait;
loff_t ki_pos;
void *private;
/* State that we remember to be able to restart/retry */
unsigned short ki_opcode;
size_t ki_nbytes; /* copy of iocb->aio_nbytes */
char __user *ki_buf; /* remaining iocb->aio_buf */
size_t ki_left; /* remaining bytes */
struct iovec ki_inline_vec; /* inline vector */
struct iovec *ki_iovec;
unsigned long ki_nr_segs;
unsigned long ki_cur_seg;
struct list_head ki_list; /* the aio core uses this
* for cancellation */
/*
* If the aio_resfd field of the userspace iocb is not zero,
* this is the underlying file* to deliver event to.
*/
struct file *ki_eventfd;
};
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
si->flags = flags;
static const struct proto_ops unix_dgram_ops = {
。。。。。。
.recvmsg = unix_dgram_recvmsg,
。。。。。。
};
看到其实进入了unix_dgram_recvmsg()钩子函数中了
static int unix_dgram_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size,
int flags)
{
struct sock_iocb *siocb = kiocb_to_siocb(iocb);
struct scm_cookie tmp_scm;
struct sock *sk = sock->sk;
struct unix_sock *u = unix_sk(sk);
int noblock = flags & MSG_DONTWAIT;
struct sk_buff *skb;
int err;
err = -EOPNOTSUPP;
if (flags&MSG_OOB)
goto out;
msg->msg_namelen = 0;
mutex_lock(&u->readlock);
skb = skb_recv_datagram(sk, flags, noblock, &err);
if (!skb) {
unix_state_lock(sk);
/* Signal EOF on disconnected non-blocking SEQPACKET socket. */
if (sk->sk_type == SOCK_SEQPACKET && err == -EAGAIN &&
(sk->sk_shutdown & RCV_SHUTDOWN))
err = 0;
unix_state_unlock(sk);
goto out_unlock;
}
wake_up_interruptible_sync(&u->peer_wait);
if (msg->msg_name)
unix_copy_addr(msg, skb->sk);
if (size > skb->len)
size = skb->len;
else if (size < skb->len)
msg->msg_flags |= MSG_TRUNC;
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, size);
if (err)
goto out_free;
if (!siocb->scm) {
siocb->scm = &tmp_scm;
memset(&tmp_scm, 0, sizeof(tmp_scm));
}
siocb->scm->creds = *UNIXCREDS(skb);
unix_set_secdata(siocb->scm, skb);
if (!(flags & MSG_PEEK))
{
if (UNIXCB(skb).fp)
unix_detach_fds(siocb->scm, skb);
}
else
{
/* It is questionable: on PEEK we could:
- do not return fds - good, but too simple 8)
- return fds, and do not return them on read (old strategy,
apparently wrong)
- clone fds (I chose it for now, it is the most universal
solution)
POSIX 1003.1g does not actually define this clearly
at all. POSIX 1003.1g doesn't define a lot of things
clearly however!
*/
if (UNIXCB(skb).fp)
siocb->scm->fp = scm_fp_dup(UNIXCB(skb).fp);
}
err = size;
scm_recv(sock, msg, siocb->scm, flags);
out_free:
skb_free_datagram(sk,skb);
out_unlock:
mutex_unlock(&u->readlock);
out:
return err;
}
struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags,
int noblock, int *err)
{
int peeked;
return __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),
&peeked, err);
}
wake_up_interruptible_sync(&u->peer_wait);
if (msg->msg_name)
unix_copy_addr(msg, skb->sk);
static void unix_copy_addr(struct msghdr *msg, struct sock *sk)
{
struct unix_sock *u = unix_sk(sk);
msg->msg_namelen = 0;
if (u->addr) {
msg->msg_namelen = u->addr->len;
memcpy(msg->msg_name, u->addr->name, u->addr->len);
}
}
if (size > skb->len)
size = skb->len;
else if (size < skb->len)
msg->msg_flags |= MSG_TRUNC;
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, size);
int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset,
struct iovec *to, int len)
{
int start = skb_headlen(skb);
int i, copy = start - offset;
/* Copy header. */
if (copy > 0) {
if (copy > len)
copy = len;
if (memcpy_toiovec(to, skb->data + offset, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
}
/* Copy paged appendix. Hmm... why does this look so complicated? */
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
int end;
BUG_TRAP(start <= offset + len);
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0) {
int err;
u8 *vaddr;
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
struct page *page = frag->page;
if (copy > len)
copy = len;
vaddr = kmap(page);
err = memcpy_toiovec(to, vaddr + frag->page_offset +
offset - start, copy);
kunmap(page);
if (err)
goto fault;
if (!(len -= copy))
return 0;
offset += copy;
}
start = end;
}
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *list = skb_shinfo(skb)->frag_list;
for (; list; list = list->next) {
int end;
BUG_TRAP(start <= offset + len);
end = start + list->len;
if ((copy = end - offset) > 0) {
if (copy > len)
copy = len;
if (skb_copy_datagram_iovec(list,
offset - start,
to, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
}
start = end;
}
}
if (!len)
return 0;
fault:
return -EFAULT;
}
siocb->scm->creds = *UNIXCREDS(skb);
#define UNIXCREDS(skb) (&UNIXCB((skb)).creds)
#define UNIXCB(skb) (*(struct unix_skb_parms*)&((skb)->cb))
struct unix_skb_parms {
struct ucred creds; /* Skb credentials */
struct scm_fp_list *fp; /* Passed files */
#ifdef CONFIG_SECURITY_NETWORK
u32 secid; /* Security ID */
#endif
};
也就是将接收到的数据包中的载运着服务器端的身份信息也拷贝到我们这里提到过的scm_cookie接收缓冲区中,注意这里层层的指针就代表着总体是在siocb结构中的,而等号后边指针符号*是取后面指针处的内容,这里有些朋友可容易混淆,这就是指针的一次性复制,将所以数据结构中的内容都复制给了scm_cookie中的creds处的结构。通过上面我们可以看出数据复制到了我们提供的缓冲区中iovec中了,而附加的信息都在scm_cookie结构中了,我们继续往下看代码
unix_detach_fds(siocb->scm, skb);
!(flags & MSG_PEEK)
static void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb)
{
int i;
scm->fp = UNIXCB(skb).fp;
skb->destructor = sock_wfree;
UNIXCB(skb).fp = NULL;
for (i=scm->fp->count-1; i>=0; i--)
unix_notinflight(scm->fp->fp[i]);
}
上面,我们看到他将struct unix_skb_parms 结构中的struct scm_fp_list文件列表也一次复制给了我们这里的scm_cookie接收附加信息的结构,同时为数据缓冲区准备了释放函数
scm->fp = UNIXCB(skb).fp;
skb->destructor = sock_wfree;
void unix_notinflight(struct file *fp)
{
struct sock *s = unix_get_socket(fp);
if(s) {
struct unix_sock *u = unix_sk(s);
spin_lock(&unix_gc_lock);
BUG_ON(list_empty(&u->link));
if (atomic_dec_and_test(&u->inflight))
list_del_init(&u->link);
unix_tot_inflight--;
spin_unlock(&unix_gc_lock);
}
}
static struct sock *unix_get_socket(struct file *filp)
{
struct sock *u_sock = NULL;
struct inode *inode = filp->f_path.dentry->d_inode;
/*
* Socket ?
*/
if (S_ISSOCK(inode->i_mode)) {
struct socket * sock = SOCKET_I(inode);
struct sock * s = sock->sk;
/*
* PF_UNIX ?
*/
if (s && sock->ops && sock->ops->family == PF_UNIX)
u_sock = s;
}
return u_sock;
}
if (UNIXCB(skb).fp)
siocb->scm->fp = scm_fp_dup(UNIXCB(skb).fp);
这里仅仅把服务器进程的打列文件列表复制过来,而没有进行清理操作。接着我们看到 scm_recv(sock, msg, siocb->scm, flags);
static __inline__ void scm_recv(struct socket *sock, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!msg->msg_control)
{
if (test_bit(SOCK_PASSCRED, &sock->flags) || scm->fp)
msg->msg_flags |= MSG_CTRUNC;
scm_destroy(scm);
return;
}
if (test_bit(SOCK_PASSCRED, &sock->flags))
put_cmsg(msg, SOL_SOCKET, SCM_CREDENTIALS, sizeof(scm->creds), &scm->creds);
scm_passec(sock, msg, scm);
if (!scm->fp)
return;
scm_detach_fds(msg, scm);
}
我们首先看到要对msghdr是否为附加信息准备了缓冲区,如果没有scm_destroy(scm);就销毁了我们的scm_cookie结构,实际上就是把其缓冲区释放掉而结构体因为是局部变量所以不需要释放。接着put_cmsg把这些附加信息复制到msghdr结构中。下面是对传递过来的打开文件授权进行处理scm_detach_fds
void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
{
struct cmsghdr __user *cm
= (__force struct cmsghdr __user*)msg->msg_control;
int fdmax = 0;
int fdnum = scm->fp->count;
struct file **fp = scm->fp->fp;
int __user *cmfptr;
int err = 0, i;
if (MSG_CMSG_COMPAT & msg->msg_flags) {
scm_detach_fds_compat(msg, scm);
return;
}
if (msg->msg_controllen > sizeof(struct cmsghdr))
fdmax = ((msg->msg_controllen - sizeof(struct cmsghdr))
/ sizeof(int));
if (fdnum < fdmax)
fdmax = fdnum;
for (i=0, cmfptr=(__force int __user *)CMSG_DATA(cm); i<fdmax;
i++, cmfptr++)
{
int new_fd;
err = security_file_receive(fp[i]);
if (err)
break;
err = get_unused_fd_flags(MSG_CMSG_CLOEXEC & msg->msg_flags
? O_CLOEXEC : 0);
if (err < 0)
break;
new_fd = err;
err = put_user(new_fd, cmfptr);
if (err) {
put_unused_fd(new_fd);
break;
}
/* Bump the usage count and install the file. */
get_file(fp[i]);
fd_install(new_fd, fp[i]);
}
if (i > 0)
{
int cmlen = CMSG_LEN(i*sizeof(int));
err = put_user(SOL_SOCKET, &cm->cmsg_level);
if (!err)
err = put_user(SCM_RIGHTS, &cm->cmsg_type);
if (!err)
err = put_user(cmlen, &cm->cmsg_len);
if (!err) {
cmlen = CMSG_SPACE(i*sizeof(int));
msg->msg_control += cmlen;
msg->msg_controllen -= cmlen;
}
}
if (i < fdnum || (fdnum && fdmax <= 0))
msg->msg_flags |= MSG_CTRUNC;
/*
* All of the files that fit in the message have had their
* usage counts incremented, so we just free the list.
*/
__scm_destroy(scm);
}
上面程序中,fdmax是msghdr需要为这些文件指针准备的接收缓冲区数量,fdnum是服务器端传过来的总共打开的文件数。这段代码就是将接受到的文件ID号安装到进程的打开文件号列表中。主要是一系列的复制操作,建议朋友们有以后的文件系统的分析后再回来读一下这段代码,如果有基础的朋友这段代码不是难事。
最后我们看到通过skb_free_datagram(sk,skb);把sk_buff的缓冲区释放掉了。因为数据和附加信息都已经保存在msghdr以及scm_cookie中了,从这里可以看出sk_buff是与底层密切相关的数据缓冲区,而msghdr是与上层密切了,所以我们说到缓冲区结构时,总是给一种感觉“不是已经有一个缓冲区结构了”的误解,必竟TCP协议和UDP协议是这样规定的。我们层层返回到sock_recvmsg()函数中,下一步是调用了wait_on_sync_kiocb
ssize_t wait_on_sync_kiocb(struct kiocb *iocb)
{
while (iocb->ki_users) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (!iocb->ki_users)
break;
io_schedule();
}
__set_current_state(TASK_RUNNING);
return iocb->ki_user_data;
}
- 10-socket的实践到内核--UDP的socket数据的接收
- 10-socket的实践到内核--UDP的socket数据的接收
- 12-socket的实践到内核--TCP的socket数据的接收
- 12-socket的实践到内核--TCP的socket数据的接收
- 11-socket的实践到内核--UDP的socket数据的发送
- 11-socket的实践到内核--UDP的socket数据的发送
- 1--socket的实践到内核--socket实践练习
- 1--socket的实践到内核--socket实践练习 .
- 7-socket的实践到内核--socket的监听
- 14-socket的实践到内核--socket的关闭
- 7-socket的实践到内核--socket的监听 .
- 14-socket的实践到内核--socket的关闭
- 13-socket的实践到内核--TCP的socket数据的发送
- 13-socket的实践到内核--TCP的socket数据的发送
- 3-socket的实践到内核--追踪socket到内核
- 3-socket的实践到内核--追踪socket到内核 .
- Android socket通过UDP的方式发送,接收数据
- 9-socket的实践到内核--client调用connect
- 9-socket的实践到内核--client调用connect
- 正则表达式的学习
- Extracting Unique Elements From a List
- uml
- asp连接 Oracle
- 10-socket的实践到内核--UDP的socket数据的接收
- 实数相等的判断
- ubuntu 下安装文件总结
- vs2005开发时ASPAJAXExtSetup.msi安装失败的解决
- 如何在VC的release编译中使用断点调试
- 转,mysql的select * into
- 11-socket的实践到内核--UDP的socket数据的发送
- Android Density (转)
- JAVA面试题解惑系列(四)——final、finally和finalize的区别